cli icon indicating copy to clipboard operation
cli copied to clipboard

Implement `ensureAuthenticatedAdminAsApp` for Bulk Operations

Open ericlee878 opened this issue 1 month ago β€’ 4 comments

Resolves: https://github.com/orgs/shop/projects/208/views/34?pane=issue&itemId=140363951&issue=shop%7Cissues-api-foundations%7C1128 Inspired by: https://github.com/Shopify/cli/pull/6596#discussion_r2543018389

Background

The bulk operations execute command currently authenticates using the CLI user's credentials, which has two critical limitations:

  1. No access to $app metafields - Cannot query or mutate app-specific metafields
  2. Ignores app scopes - Does not respect the app's configured access scopes.

This prevents bulk operations from working properly with app-owned data.

Solution

Implemented ensureAuthenticatedAdminAsApp() that authenticates as the app using OAuth 2.0 client credentials grant. This provides:

  • Full access to $app metafields
  • Respects the app's configured scopes
  • Proper app-context authentication

This idea originally came from a hack days.

Implementation Notes

  • The API secret extraction pattern follows the existing approach in webhook/trigger-options.ts.
  • Currently both app and remoteApp are passed to executeBulkOperation. The app parameter is only used for:
    • Display name (app.name)
    • Client ID (app.configuration.client_id)

Both values are also available on remoteApp as title and apiKey. Could be simplified in the future to only use remoteApp for consistency.

Testing

To test with this new authentification, you must install the app on the store. You can do this by:

cd <PATH_TO_TEST_APP>
pnpm shopify app dev

Then, we can use the existing command works unchanged:

For queries:

pnpm shopify:run app execute \
  --path <PATH_TO_TEST_APPP> \
  -q '{ products(first: 10) { edges { node { id title } } } }'

For mutations:

pnpm shopify:run app execute \
  --path <PATH_TO_TEST_APPP> \
  -q 'mutation productUpdate($input: ProductInput!) { productUpdate(input: $input) { product { id tags } userErrors { field message } } }' \
  -v '[{"input": {"id": "gid://shopify/Product/9721830834416", "tags": ["bulk-test"]}}]'

Now authenticates as the app instead of the CLI user, enabling access to app-scoped resources.

ericlee878 avatar Nov 19 '25 23:11 ericlee878

This stack of pull requests is managed by Graphite. Learn more about stacking.

ericlee878 avatar Nov 19 '25 23:11 ericlee878

Coverage report

St.:grey_question:
Category Percentage Covered / Total
🟑 Statements
79.27% (+0.04% πŸ”Ό)
13725/17314
🟑 Branches
73.17% (+0.06% πŸ”Ό)
6699/9155
🟑 Functions
79.42% (+0.04% πŸ”Ό)
3530/4445
🟑 Lines
79.64% (+0.06% πŸ”Ό)
12970/16286
Show new covered files 🐣
St.:grey_question:
File Statements Branches Functions Lines
🟒
... / admin-as-app.ts
100% 100% 100% 100%
🟒
... / bulk-operation-run-mutation.ts
100% 100% 100% 100%
🟒
... / bulk-operation-run-query.ts
100% 100% 100% 100%
🟒
... / get-bulk-operation-by-id.ts
100% 100% 100% 100%
🟒
... / staged-uploads-create.ts
100% 100% 100% 100%
🟒
... / download-bulk-operation-results.ts
100% 100% 100% 100%
🟒
... / execute-bulk-operation.ts
90.91% 80.56% 100% 92.31%
🟒
... / format-bulk-operation-status.ts
100% 100% 100% 100%
🟒
... / run-mutation.ts
100% 100% 100% 100%
🟒
... / run-query.ts
100% 100% 100% 100%
🟑
... / stage-file.ts
72.73% 62.5% 83.33% 71.88%
🟒
... / watch-bulk-operation.ts
100% 100% 100% 100%
Show files with reduced coverage πŸ”»
St.:grey_question:
File Statements Branches Functions Lines
🟑
... / specification.ts
68.52% (-0.57% πŸ”»)
75.61% (+2.44% πŸ”Ό)
76.47% (-1.31% πŸ”»)
68.09% (-0.66% πŸ”»)
🟒
... / developer-platform-client.ts
84.62% (-1.5% πŸ”»)
73.68% (+3.1% πŸ”Ό)
81.82% (+1.82% πŸ”Ό)
90.63% (-2.71% πŸ”»)
🟒
... / api.ts
87.07% (-0.43% πŸ”»)
76.71% (-0.1% πŸ”»)
100%
86.49% (-0.43% πŸ”»)
🟒
... / ConcurrentOutput.tsx
98.36% (-1.64% πŸ”»)
92% (-4% πŸ”»)
100%
98.33% (-1.67% πŸ”»)
πŸ”΄
... / ui.tsx
50.82% (-0.79% πŸ”»)
42.86% (-5.53% πŸ”»)
54.55% (+1.42% πŸ”Ό)
50% (-0.82% πŸ”»)
🟒
... / console.ts
81.82% (+15.15% πŸ”Ό)
75% (-25% πŸ”»)
100% (+33.33% πŸ”Ό)
81.82% (+15.15% πŸ”Ό)
πŸ”΄
... / dev.ts
12.77% (-0.57% πŸ”»)
2.78% (-0.16% πŸ”»)
57.14%
12.77% (-0.57% πŸ”»)
🟑
... / theme-environment.ts
69.57% (-1.86% πŸ”»)
50%
55.56% (-3.27% πŸ”»)
69.57% (-1.86% πŸ”»)

Test suite run success

3406 tests passing in 1386 suites.

Report generated by πŸ§ͺjest coverage report action from 02b54eddab964b222f3b837f4015f597f63370e6

github-actions[bot] avatar Nov 19 '25 23:11 github-actions[bot]

We detected some changes at packages/*/src and there are no updates in the .changeset. If the changes are user-facing, run pnpm changeset add to track your changes and include them in the next release CHANGELOG.

[!CAUTION] DO NOT create changesets for features which you do not wish to be included in the public changelog of the next CLI release.

github-actions[bot] avatar Nov 19 '25 23:11 github-actions[bot]

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
@@ -97,6 +97,17 @@ export declare function ensureAuthenticatedStorefront(scopes?: StorefrontRendere
  * @returns The access token for the Admin API.
  */
 export declare function ensureAuthenticatedAdmin(store: string, scopes?: AdminAPIScope[], options?: EnsureAuthenticatedAdditionalOptions): Promise<AdminSession>;
+/**
+ * Ensure that we have a valid Admin session for the given store, acting on behalf of the app.
+ *
+ * This will fail if the app has not already been installed.
+ *
+ * @param storeFqdn - Store fqdn to request auth for.
+ * @param apiKey - API key for the app.
+ * @param apiSecret - API secret for the app.
+ * @returns The access token for the Admin API.
+ */
+export declare function ensureAuthenticatedAdminAsApp(storeFqdn: string, apiKey: string, apiSecret: string): Promise<AdminSession>;
 /**
  * Ensure that we have a valid session to access the Theme API.
  * If a password is provided, that token will be used against Theme Access API.

github-actions[bot] avatar Nov 20 '25 19:11 github-actions[bot]

Merge activity

  • Nov 26, 7:37 PM UTC: This pull request can not be added to the Graphite merge queue. Please try rebasing and resubmitting to merge when ready.
  • Nov 26, 7:37 PM UTC: Graphite disabled "merge when ready" on this PR due to: a merge conflict with the target branch; resolve the conflict and try again..

graphite-app[bot] avatar Nov 26 '25 19:11 graphite-app[bot]

Yeah, sorry for the outdated description -- I'll update at least the title so it'll be clearer to future readers what this PR is actually doing.

Thanks for the quick review!

jordanverasamy avatar Nov 27 '25 16:11 jordanverasamy