Skip to content

Authenticate store and organization commands with an automation token - #8729

Merged
egaodd merged 1 commit into
mainfrom
eddie/org-token-2-bp-auth
Oct 8, 2026
Merged

egaodd merged 1 commit into
mainfrom
eddie/org-token-2-bp-auth

Conversation

@egaodd

@egaodd egaodd commented Oct 1, 2026 •

Copy link
Copy Markdown

Part of shop/issues-develop#24004

TL;DR: store commands and organization list authenticate with an exported automation token instead of asking for a login. Callers that don't opt in, including Hydrogen's login, are unchanged, and app commands exchange tokens exactly as before.

  1. Authenticate store and organization commands with an automation token #8729: store and organization commands authenticate with an exported automation token ← this one
  2. Add SHOPIFY_ORGANIZATION_AUTOMATION_TOKEN #8731: add SHOPIFY_ORGANIZATION_AUTOMATION_TOKEN and the selection rules

WHY are these changes introduced?

These commands can't run unattended today. store list, store info, store create dev, store delete and organization list authenticate through ensureAuthenticatedBusinessPlatform, which ignores automation tokens. Without a cached login they start device auth.

Reading the token isn't enough on its own. The automation exchange asks Identity only for destinations.readonly. Business Platform's organizations API, which store list, create and delete call, accepts a CLI token only if it carries organization.user-management, organization.store-management or organization.partner-dashboard-access (authorized_clients in Business Platform's organizations/graphql_controller.rb). A store-capable token would still be refused.

Why not request that scope by name. Identity rejects a request for any scope the token doesn't hold, so asking for organization.store-management would break every app and Partner token. With no scope in the request, Identity issues the token's own scopes, narrowed to Business Platform. Every token that works today keeps destinations.readonly: we already request it by name, so any token that exchanges successfully holds it.

WHAT is this pull request doing?

  • Opt-in option. ensureAuthenticatedBusinessPlatform takes allowAutomationToken. When it's set and getAppAutomationToken() returns a token, the helper exchanges that token for a Business Platform token with no explicit scopes. It never starts an App Management exchange or reads the session store.
  • The store and organizations packages opt in at all 8 call sites, including the 401 retry handler, so a retry re-exchanges the same token.
  • Opt-in rather than on by default because the bundled Hydrogen plugin uses this cli-kit, and hydrogen login, link, list and customer-account-push call this helper for a human login. Callers that don't opt in keep the logged-in session.
  • App commands are untouched. ensureAuthenticatedAppManagementAndBusinessPlatform still requests destinations.readonly. The no-scope request only applies to the opted-in path.

What changes for existing tokens:

  • Store commands and organization list use an exported app or Partner token instead of a cached login. organization list returns the token's organization. Store list, create and delete are refused, because those tokens have no store access. That's the intended "no fallback to the logged-in user".
  • app dev's "create a new store" option goes through the same create path, so it also uses an exported token.

No changeset. Organization tokens can't be issued yet, so there's nothing to announce. The changelog entry lands with the launch docs (shop/issues-develop#24014). Until shop/issues-develop#24000 can issue store-capable tokens, the only visible effect is the store-command change above, for people with an app or Partner token exported.

Not in this PR: store info uses a saved preview-store session directly, and falls back to a saved shopify store auth login when the Business Platform lookup can't find the store.

How to manually test your changes?

With an App Automation Token and no cached login (shopify auth logout):

SHOPIFY_APP_AUTOMATION_TOKEN=<token> shopify organization list

This lists the token's organization instead of asking you to log in.

SHOPIFY_APP_AUTOMATION_TOKEN=<token> shopify store list --organization-id <id>

This fails with an authorization error instead of asking you to log in.

Without the variable, both behave as on main. Trying a store-capable token needs shop/issues-develop#24000, or the alex/organization-token-full-e2e-demo World branch on a local rig.

Checklist

  • I've considered possible cross-platform impacts (Mac, Linux, Windows)
  • I've considered possible documentation changes
  • I've considered analytics changes to measure impact
  • The change is user-facing — I've identified the correct bump type (patch for bug fixes · minor for new features · major for breaking changes) and added a changeset with pnpm changeset add

@github-actions github-actions Bot added the Area: @shopify/cli @shopify/cli package issues label Oct 1, 2026
@egaodd
egaodd marked this pull request as ready for review October 1, 2026 22:41
@egaodd
egaodd requested a review from a team as a code owner October 1, 2026 22:41
Comment thread packages/cli-kit/src/private/node/session/scopes.ts Outdated
Comment thread .changeset/store-commands-automation-token.md Outdated
@egaodd
egaodd force-pushed the eddie/org-token-2-bp-auth branch from bb31926 to b0f0ced Compare October 2, 2026 16:52
@egaodd
egaodd requested a review from a team as a code owner October 2, 2026 16:52
@egaodd
egaodd changed the base branch from eddie/org-token-1-selector to main October 2, 2026 16:52
@egaodd
egaodd force-pushed the eddie/org-token-2-bp-auth branch 2 times, most recently from b0f0ced to 5630002 Compare October 2, 2026 16:56
@github-actions github-actions Bot added no-changelog This PR doesn't include a changeset entry. Is an internal only change not relevant to end users. and removed Area: @shopify/cli @shopify/cli package issues labels Oct 2, 2026
ensureAuthenticatedBusinessPlatform now takes an allowAutomationToken option.
When a caller opts in and SHOPIFY_APP_AUTOMATION_TOKEN or
SHOPIFY_CLI_PARTNERS_TOKEN is set, it exchanges that token for a Business
Platform token instead of using the logged-in session. The store and
organizations packages opt in at every call site, including the 401 retry
handler. Other callers, such as Hydrogen's login, keep the user's session.

The opted-in exchange requests no explicit scopes, so Identity issues every
Business Platform scope the token holds. Business Platform's organizations API
admits a CLI token only with a scope such as organization.store-management,
and requesting that scope by name would fail for app and Partner tokens, which
don't hold it. App commands keep requesting destinations.readonly as before.

Co-authored-by: Eddie Gao <eddie.gao@shopify.com>
@egaodd
egaodd force-pushed the eddie/org-token-2-bp-auth branch from 5630002 to 6977478 Compare October 7, 2026 14:20
@github-actions

github-actions Bot commented Oct 7, 2026

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/public/node/session.d.ts
@@ -128,14 +128,25 @@ export declare function ensureAuthenticatedAdmin(store: string, scopes?: AdminAP
  * @returns The access token and store.
  */
 export declare function ensureAuthenticatedThemes(store: string, password: string | undefined, scopes?: AdminAPIScope[], options?: EnsureAuthenticatedAdditionalOptions): Promise<AdminSession>;
+/**
+ * Options for `ensureAuthenticatedBusinessPlatform`.
+ */
+export interface EnsureAuthenticatedBusinessPlatformOptions extends EnsureAuthenticatedAdditionalOptions {
+    /**
+     * Authenticate with the automation token set in the environment, when there is one, instead of the
+     * logged-in user. Only commands that support automation tokens opt in; other callers, such as
+     * Hydrogen's login, keep using the user's session.
+     */
+    allowAutomationToken?: boolean;
+}
 /**
  * Ensure that we have a valid session to access the Business Platform API.
  *
- * @param scopes - Optional array of extra scopes to authenticate with.
+ * @param scopes - Optional array of extra scopes to authenticate with. Ignored when an automation token is used.
  * @param options - Optional extra options to use.
  * @returns The access token for the Business Platform API.
  */
-export declare function ensureAuthenticatedBusinessPlatform(scopes?: BusinessPlatformScope[], options?: EnsureAuthenticatedAdditionalOptions): Promise<string>;
+export declare function ensureAuthenticatedBusinessPlatform(scopes?: BusinessPlatformScope[], options?: EnsureAuthenticatedBusinessPlatformOptions): Promise<string>;
 /**
  * Logout from Shopify.
  *
packages/cli-kit/dist/private/node/session/exchange.d.ts
@@ -45,9 +45,10 @@ export declare function exchangeAppAutomationTokenForAppManagementAccessToken(to
 /**
  * Given a custom app automation token passed as ENV variable, request a valid Business Platform API token.
  * @param token - The app automation token passed as ENV variable `SHOPIFY_APP_AUTOMATION_TOKEN`
+ * @param scopes - The scopes to request. An empty list makes Identity issue every Business Platform scope the token holds.
  * @returns An instance with the application access tokens.
  */
-export declare function exchangeAppAutomationTokenForBusinessPlatformAccessToken(token: string): Promise<{
+export declare function exchangeAppAutomationTokenForBusinessPlatformAccessToken(token: string, scopes?: string[]): Promise<{
     accessToken: string;
     userId: string;
 }>;

@egaodd
egaodd requested a review from isaacroldan October 7, 2026 15:24

@alexanderMontague alexanderMontague left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tophatted and reviewed, no issues here. All looks good, existing flows still succeed for both a human session and an existing app automation token. Thanks!

@egaodd
egaodd added this pull request to the merge queue Oct 8, 2026
Merged via the queue into main with commit ce4fac6 Oct 8, 2026
148 of 155 checks passed
@egaodd
egaodd deleted the eddie/org-token-2-bp-auth branch October 8, 2026 14:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

no-changelog This PR doesn't include a changeset entry. Is an internal only change not relevant to end users.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants