Pass Storefront API requests through the dev proxy as-is#7377
Pass Storefront API requests through the dev proxy as-is#7377
Conversation
Requests to /api/{version|unstable}/graphql.json were returning 401
because the dev proxy injected the SFR devtools bearer via the
Authorization header. The public Storefront API rejects that token —
it expects X-Shopify-Storefront-Access-Token from the caller.
Forward these requests untouched (no Authorization, Cookie, referer,
or _fd/pb params) so the caller's own auth passes through. Introduces
isPassthroughRequest(event) as the extension point for future
pass-through rules.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Adjusts the theme dev proxy so Storefront API GraphQL requests aren’t broken by theme-dev auth injection, preventing 401s when callers use their own Storefront API credentials.
Changes:
- Adds a Storefront API path matcher and
isPassthroughRequest(event)hook for future passthrough rules. - Skips adding theme auth/cookies/dev query params for
/api/{version|unstable}/graphql.jsonrequests. - Adds Vitest coverage for Storefront API passthrough behavior and a patch changeset.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| packages/theme/src/cli/utilities/theme-environment/proxy.ts | Introduces SFAPI passthrough detection and conditionally avoids injecting theme-specific headers/query params. |
| packages/theme/src/cli/utilities/theme-environment/proxy.test.ts | Adds tests validating the passthrough path and non-matching fallback behavior. |
| .changeset/itchy-jobs-report.md | Patch changeset documenting the SFAPI proxy fix. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| Cookie: buildCookies(ctx.session, {headers}), | ||
| // Only include Authorization for theme dev, not theme-extensions | ||
| ...(ctx.type === 'theme' ? {Authorization: `Bearer ${ctx.session.storefrontToken}`} : {}), | ||
| }) |
There was a problem hiding this comment.
isPassthroughRequest skips the injection block, but the passthrough path still forwards any client-supplied cookie, authorization, and referer headers (from getProxyStorefrontHeaders(event)), and also forwards _fd/pb if they were already present in the incoming URL. This contradicts the stated intent of not sending auth/cookies/dev params to the public Storefront API, and can also leak caller Authorization tokens upstream. Consider explicitly deleting cookie/Cookie, authorization/Authorization, and referer from headers (and deleting _fd/pb from url.searchParams) when isPassthroughRequest(event) is true.
| }) | |
| }) | |
| } else { | |
| delete headers.cookie | |
| delete headers.Cookie | |
| delete headers.authorization | |
| delete headers.Authorization | |
| delete headers.referer | |
| delete headers.Referer | |
| url.searchParams.delete('_fd') | |
| url.searchParams.delete('pb') |
| test('forwards /api/YYYY-MM/graphql.json without injecting theme auth, cookies, referer, or dev params', async () => { | ||
| const event = createH3Event('POST', '/api/2026-01/graphql.json', { | ||
| 'x-shopify-storefront-access-token': 'public-access-token', | ||
| authorization: 'Bearer client-supplied-token', | ||
| }) | ||
|
|
||
| await proxyStorefrontRequest(event, passthroughCtx) | ||
|
|
||
| expect(fetchMock).toHaveBeenCalledOnce() | ||
| const [requestUrl, init] = fetchMock.mock.calls[0] as [URL, RequestInit] | ||
|
|
||
| expect(requestUrl.toString()).toBe('https://my-store.myshopify.com/api/2026-01/graphql.json') | ||
| expect(requestUrl.searchParams.has('_fd')).toBe(false) | ||
| expect(requestUrl.searchParams.has('pb')).toBe(false) | ||
|
|
||
| const headers = init.headers as Record<string, string> | ||
| expect(headers['x-shopify-storefront-access-token']).toBe('public-access-token') | ||
| expect(headers.authorization).toBe('Bearer client-supplied-token') | ||
| expect(headers.Authorization).toBeUndefined() | ||
| expect(headers.Cookie).toBeUndefined() | ||
| expect(headers.referer).toBeUndefined() | ||
| }) |
There was a problem hiding this comment.
The passthrough tests currently assert headers.Cookie/headers.Authorization are undefined, but getProxyStorefrontHeaders() produces lowercased keys (see existing snapshot in this file), so these assertions don’t prove that cookies/authorization aren’t being forwarded (they could still be present as headers.cookie / headers.authorization). If the goal is to ensure nothing auth/cookie-like is forwarded for SFAPI, update the test to set cookie/referer/authorization on the incoming request and assert both casings are absent (or, if forwarding caller auth is intended, clarify expectations and ensure the proxy doesn’t overwrite it).
There was a problem hiding this comment.
Before this gets merged let's get @EvilGenius13 to look at it who has done a bunch of header stuff recently to prevent 401 and 429s.
WHY are these changes introduced?
Requests to /api/{version|unstable}/graphql.json were returning 401 because the dev proxy injected the SFR devtools bearer via the Authorization header. The public Storefront API rejects that token — it expects X-Shopify-Storefront-Access-Token from the caller.
WHAT is this pull request doing?
Forward these requests untouched (no Authorization, Cookie, referer, or _fd/pb params) so the caller's own auth passes through. Introduces isPassthroughRequest(event) as the extension point for future pass-through rules.
How to test your changes?
Make a request to SFAPI from the Chrome dev tools. Without this PR it will return 401.
Post-release steps
Checklist
patchfor bug fixes ·minorfor new features ·majorfor breaking changes) and added a changeset withpnpm changeset add