Repository navigation
Authenticate store and organization commands with an automation token - #8729
Merged
Merged
Conversation
This was referenced Oct 1, 2026
egaodd
marked this pull request as ready for review
October 1, 2026 22:41
isaacroldan
reviewed
Oct 2, 2026
egaodd
force-pushed
the
eddie/org-token-2-bp-auth
branch
from
October 2, 2026 16:52
bb31926 to
b0f0ced
Compare
egaodd
force-pushed
the
eddie/org-token-2-bp-auth
branch
2 times, most recently
from
October 2, 2026 16:56
b0f0ced to
5630002
Compare
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
force-pushed
the
eddie/org-token-2-bp-auth
branch
from
October 7, 2026 14:20
5630002 to
6977478
Compare
Contributor
Differences in type declarationsWe 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:
New type declarationsWe found no new type declarations in this PR Existing type declarationspackages/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;
}>;
|
alexanderMontague
approved these changes
Oct 7, 2026
alexanderMontague
left a comment
Contributor
There was a problem hiding this comment.
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!
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Part of shop/issues-develop#24004
TL;DR:
storecommands andorganization listauthenticate 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.SHOPIFY_ORGANIZATION_AUTOMATION_TOKENand the selection rulesWHY are these changes introduced?
These commands can't run unattended today.
store list,store info,store create dev,store deleteandorganization listauthenticate throughensureAuthenticatedBusinessPlatform, 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 carriesorganization.user-management,organization.store-managementororganization.partner-dashboard-access(authorized_clientsin Business Platform'sorganizations/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-managementwould 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 keepsdestinations.readonly: we already request it by name, so any token that exchanges successfully holds it.WHAT is this pull request doing?
ensureAuthenticatedBusinessPlatformtakesallowAutomationToken. When it's set andgetAppAutomationToken()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.cli-kit, andhydrogen login,link,listandcustomer-account-pushcall this helper for a human login. Callers that don't opt in keep the logged-in session.ensureAuthenticatedAppManagementAndBusinessPlatformstill requestsdestinations.readonly. The no-scope request only applies to the opted-in path.What changes for existing tokens:
organization listuse an exported app or Partner token instead of a cached login.organization listreturns 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):This lists the token's organization instead of asking you to log in.
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 thealex/organization-token-full-e2e-demoWorld branch on a local rig.Checklist
patchfor bug fixes ·minorfor new features ·majorfor breaking changes) and added a changeset withpnpm changeset add