Skip to content

[Prototype] Authenticate CLI with organization automation tokens - #8597

Draft
alexanderMontague wants to merge 3 commits into
mainfrom
alex/organization-token-full-e2e-demo
Draft

alexanderMontague wants to merge 3 commits into
mainfrom
alex/organization-token-full-e2e-demo

Conversation

@alexanderMontague

Copy link
Copy Markdown
Contributor

Adds unattended authentication for organization-scoped automation credentials issued by the accompanying World prototype.

SHOPIFY_ORGANIZATION_AUTOMATION_TOKEN is a first-class, mutually exclusive credential source. Supported Business Platform and App Management paths exchange it directly; unsupported commands fail instead of silently using cached or interactive human authentication. Organization discovery uses the Business Platform Destinations path through @shopify/organizations, so store-only service accounts do not need an app-management grant merely to find their organization and owned development stores.

Live local evidence against the World services:

  • ran from an isolated environment with no cached Shopify session;
  • organization list --json returned the service account’s organization;
  • store list --organization-id 1 --json returned two development stores owned by that service account;
  • the same source secret failed immediately after World revoked it;
  • app-management exchange succeeds only when the credential includes that independent capability.

Open questions for production planning:

  • final environment-variable naming and compatibility window;
  • command-by-command support matrix and error copy;
  • whether Partners-token compatibility should remain independent from organization tokens;
  • release ordering relative to Identity/BP/Core and CLI version negotiation.

Validation: 129 focused Vitest tests pass; @shopify/cli-kit and @shopify/app typechecks pass; ESLint passes for every changed TypeScript file.


Generated with Pi. Vetted by Me.

amcaplan and others added 3 commits September 19, 2026 21:40
Business Platform commands could not run with SHOPIFY_APP_AUTOMATION_TOKEN
set. ensureAuthenticated() only exchanged the environment token when
applications.partnersApi was requested, so a BP-only command fell through
to the device authorization flow and failed on throwOnNoPrompt in any
non-interactive environment. ensureAuthenticatedBusinessPlatform() now
exchanges the environment token directly, matching what
ensureAuthenticatedPartners() and ensureAuthenticatedAppManagement()
already do.

The exchange also stops asking for scopes. It previously requested
destinations.readonly, and widening that to include
organization.store-management for organization-scoped tokens turned out
to break app-scoped ones: Identity refuses to grant a scope the subject
token does not already hold, so an app-scoped token was rejected with

    invalid_request :: Invalid 'scope' value: new scopes must not be
    specified

Requesting no scope makes Identity grant whatever the token carries,
narrowed to Business Platform. An organization-scoped token comes back
with store management and an app-scoped token does not, which is the
distinction we want, and neither has to be special-cased here. An empty
scope string behaves the same as omitting the parameter, so
requestAppToken() needs no change.

Verified against local Identity with both token kinds: `organization list`
and `store list` work with either, and `store create dev` authenticates
with an organization-scoped token. An app-scoped token authenticates but
is refused by Business Platform for store management, as intended.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Assisted-By: devx/f5485e0b-dc2b-4459-9b5d-7968b926fcfb
Select organization, app, and deprecated Partners automation tokens through one canonical helper, reject ambiguous organization/app credentials, and keep automation exchanges authoritative for Business Platform and App Management retries.
@github-actions github-actions Bot added the Area: @shopify/cli @shopify/cli package issues label Sep 20, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Differences in type declarations

We detected differences in the type declarations generated by Typescript for this branch compared to the baseline ('main' branch). Please, review them to ensure they are backward-compatible. Here are some important things to keep in mind:

  • Some seemingly private modules might be re-exported through public modules.
  • If the branch is behind main you might see odd diffs, rebase main into this branch.

New type declarations

We found no new type declarations in this PR

Existing type declarations

packages/cli-kit/dist/private/node/constants.d.ts
@@ -8,6 +8,7 @@ export declare const environmentVariables: {
     env: string;
     noAnalytics: string;
     optOutInstrumentation: string;
+    organizationAutomationToken: string;
     appAutomationToken: string;
     partnersToken: string;
     runAsUser: string;
packages/cli-kit/dist/private/node/session.d.ts
@@ -104,5 +104,5 @@ export interface EnsureAuthenticatedAdditionalOptions {
  * @param options - Optional extra options to use.
  * @returns An instance with the access tokens organized by application.
  */
-export declare function ensureAuthenticated(applications: OAuthApplications, _env?: NodeJS.ProcessEnv, { forceRefresh, noPrompt, forceNewSession }?: EnsureAuthenticatedAdditionalOptions): Promise<OAuthSession>;
+export declare function ensureAuthenticated(applications: OAuthApplications, env?: NodeJS.ProcessEnv, { forceRefresh, noPrompt, forceNewSession }?: EnsureAuthenticatedAdditionalOptions): Promise<OAuthSession>;
 export {};
\ No newline at end of file
packages/cli-kit/dist/public/node/environment.d.ts
@@ -9,11 +9,27 @@
  * @returns Current process environment variables.
  */
 export declare function getEnvironmentVariables(): NodeJS.ProcessEnv;
+export interface AutomationToken {
+    value: string;
+    source: 'organization' | 'app' | 'partners';
+}
 /**
- * Returns the value of the SHOPIFY_APP_AUTOMATION_TOKEN environment variable,
- * falling back to the deprecated SHOPIFY_CLI_PARTNERS_TOKEN.
+ * Selects the automation token to use for authentication.
  *
- * @returns The app automation token value, or undefined if neither env var is set.
+ * Organization and app automation tokens are mutually exclusive because they represent different
+ * authentication subjects. The deprecated Partners token remains a fallback for compatibility.
+ *
+ * @param env - Environment variables to select the token from.
+ * @returns The selected automation token and its source, or undefined if none is set.
+ * @throws AbortError when both organization and app automation tokens are set.
+ */
+export declare function getAutomationToken(env?: NodeJS.ProcessEnv): AutomationToken | undefined;
+/**
+ * Returns the selected automation token value.
+ *
+ * Prefer getAutomationToken when the token source is needed.
+ *
+ * @returns The selected automation token value, or undefined if none is set.
  */
 export declare function getAppAutomationToken(): string | undefined;
 /**
packages/cli-kit/dist/public/node/session.d.ts
@@ -72,8 +72,8 @@ export declare function ensureAuthenticatedUser(env?: NodeJS.ProcessEnv, options
 }>;
 /**
  * Ensure that we have a valid session to access the Partners API.
- * If SHOPIFY_CLI_PARTNERS_TOKEN exists, that token will be used to obtain a valid Partners Token
- * If SHOPIFY_CLI_PARTNERS_TOKEN exists, scopes will be ignored.
+ * If an automation token exists in the environment, it will be used to obtain a valid Partners token
+ * and scopes will be ignored.
  *
  * @param scopes - Optional array of extra scopes to authenticate with.
  * @param env - Optional environment variables to use.
@@ -130,6 +130,7 @@ export declare function ensureAuthenticatedAdmin(store: string, scopes?: AdminAP
 export declare function ensureAuthenticatedThemes(store: string, password: string | undefined, scopes?: AdminAPIScope[], options?: EnsureAuthenticatedAdditionalOptions): Promise<AdminSession>;
 /**
  * Ensure that we have a valid session to access the Business Platform API.
+ * If an automation token exists in the environment, that token will be used and scopes will be ignored.
  *
  * @param scopes - Optional array of extra scopes to authenticate with.
  * @param options - Optional extra options to use.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Area: @shopify/cli @shopify/cli package issues

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants