Skip to content

feat: TRAC-268 add graphql proxy for client-side storefront API access#2825

Open
chanceaclark wants to merge 1 commit intocanaryfrom
graphql-proxy
Open

feat: TRAC-268 add graphql proxy for client-side storefront API access#2825
chanceaclark wants to merge 1 commit intocanaryfrom
graphql-proxy

Conversation

@chanceaclark
Copy link
Copy Markdown
Contributor

@chanceaclark chanceaclark commented Jan 16, 2026

What/Why?

Add a GraphQL proxy endpoint to the Catalyst proxy layer that allows client-side JavaScript (e.g., checkout-sdk-js) to make GraphQL Storefront API requests without exposing storefront tokens to the browser.

This is a foundational piece that enables:

  • PROJECT-6580 — eWallet buttons on PDP (Apple Pay, Google Pay, etc.) require checkout-sdk-js to make GraphQL calls from the browser to initialize wallet payment methods.
  • PROJECT-6074 — Saved payment methods management in My Account may require client-side GraphQL calls for payment instrument CRUD operations.

How it works

  1. Request matching — Only POST /graphql requests with a valid x-catalyst-graphql-proxy-requester header are intercepted. All others pass through.
  2. Requester allowlist — The header value must be in ALLOWED_REQUESTERS (currently: checkout-sdk-js).
  3. Unauthenticated token — Authenticates to the BigCommerce GraphQL API using BIGCOMMERCE_STOREFRONT_UNAUTHENTICATED_TOKEN, a dedicated scoped-down token separate from the primary BIGCOMMERCE_STOREFRONT_TOKEN.
  4. Customer access token — The auth() wrapper extracts the customer access token from the session and passes it via X-Bc-Customer-Access-Token for customer-specific queries (e.g., cart, wishlists).
  5. No cookie forwarding — Browser cookies are not forwarded to the GraphQL API.
  6. Body validation — Request bodies are validated with Zod before forwarding.
Browser (checkout-sdk-js)
  │  POST /graphql
  │  x-catalyst-graphql-proxy-requester: checkout-sdk-js
  │  { query, variables }
  ▼
Next.js Proxy Layer (withGraphqlProxy)
  │  Authorization: Bearer <BIGCOMMERCE_STOREFRONT_UNAUTHENTICATED_TOKEN>
  │  X-Bc-Customer-Access-Token: <from session>
  │  { query, variables }
  ▼
BigCommerce GraphQL Storefront API

Testing

Valid request to the proxy:

Screenshot 2026-01-16 at 15 48 56

Request without valid header:

Screenshot 2026-01-16 at 15 49 03

Request with invalid query (since it's a browser request):

Screenshot 2026-01-22 at 13 48 30

Request with valid query:

Screenshot 2026-01-22 at 13 48 38

Migration

See changeset for full migration steps. Key changes:

  1. Add BIGCOMMERCE_STOREFRONT_UNAUTHENTICATED_TOKEN env var
  2. Create core/proxies/with-graphql-proxy.ts
  3. Register withGraphqlProxy in core/proxy.ts (after withChannelId, before withRoutes)

@changeset-bot
Copy link
Copy Markdown

changeset-bot bot commented Jan 16, 2026

🦋 Changeset detected

Latest commit: be3f027

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@bigcommerce/catalyst-core Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@vercel
Copy link
Copy Markdown

vercel bot commented Jan 16, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
catalyst Ready Ready Preview, Comment Mar 27, 2026 10:30pm

Request Review

@chanceaclark
Copy link
Copy Markdown
Contributor Author

Marking as do not merge since this is a minor and we probably want to add it as apart of the next minor release.

customerAccessToken,
fetchOptions: {
headers: {
// DO NOT REMOVE: We pass the request cookies to emulate that this is coming from the browser to the GraphQL API
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Do we actually need this? Since we’ll be using scoped API tokens without the customer scope for the proxy endpoint, it won’t be able to access sensitive fields such as the customer access token.

The issue with forwarding all browser cookies to the underlying BC SF API is that those cookies are intended for Catalyst, not the BC SF API. They may contain sensitive data (e.g.: third-party analytics cookies), cookie names that BC might unintentionally read if they overlap (e.g.: CSRF tokens), or cause BC to assume a storefront session exists and start treating proxied requests as stateful.

Copy link
Copy Markdown
Contributor Author

@chanceaclark chanceaclark Jan 27, 2026

Choose a reason for hiding this comment

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

Do we actually need this? Since we’ll be using scoped API tokens without the customer scope for the proxy endpoint, it won’t be able to access sensitive fields such as the customer access token.

We are thinking about releasing it in the next minor version whether or not the scoped API tokens are ready or not.

The issue with forwarding all browser cookies to the underlying BC SF API is that those cookies are intended for Catalyst, not the BC SF API. They may contain sensitive data (e.g.: third-party analytics cookies), cookie names that BC might unintentionally read if they overlap (e.g.: CSRF tokens), or cause BC to assume a storefront session exists and start treating proxied requests as stateful.

We don't necessarily need to forward all the cookies then, we can pass a dummy value instead. I looked at the storefront service code, and essentially passing this header makes the request behave like it's a request coming directly from the client which applies the same rules as we want from a functionality standpoint as the scoped API token approach.

Also, we don't pass back the response Set-Cookie value back to the client so we won't be passing back stateful information back. I get the point you are trying to make here, but I don't think it's a cause for concern as we are not passing the stateful session cookies back.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

We don't necessarily need to forward all the cookies then, we can pass a dummy value instead.

Thanks for the explanation. Sending a specific cookie set by Catalyst could definitely alleviate some of my concerns. That said, I’m not 100% sure about the potential unintended side effects of this change. It’s worth checking the following to make sure it’s safe to proceed or if it's better to wait for scoped tokens.

  1. We should verify whether sending a cookie would make the request stateful, meaning starting a new BC storefront session. If so, it could have several implications, e.g.: cart access would require the corresponding session token to be sent back, spam protection might be enforced etc...
  2. This change could also impact analytics data and potentially skew metrics, since shopper activity is tracked for requests coming from browsers.
  3. Stateful requests may have to different rate limits than stateless requests. Also, I'm not sure whether including a cookie header could interfere with the trusted proxy setup.

@github-actions
Copy link
Copy Markdown
Contributor

Bundle Size Report

Comparing against baseline from 620ac0e (2026-03-27).

No bundle size changes detected.

@chanceaclark chanceaclark changed the title feat: CATALYST-1664 add graphql proxy in middleware feat: TRAC-268 add graphql proxy for client-side storefront API access Mar 27, 2026
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 27, 2026

Unlighthouse Performance Comparison — Vercel

Comparing PR preview deployment Unlighthouse scores vs production Unlighthouse scores.

Summary Score

Aggregate score across all categories as reported by Unlighthouse.

Prod Desktop Prod Mobile Preview Desktop Preview Mobile
Score 92 93 91 94

Category Scores

Category Prod Desktop Prod Mobile Preview Desktop Preview Mobile
Performance 76 93 76 94
Accessibility 95 95 95 98
Best Practices 100 100 95 100
SEO 100 100 100 100

Core Web Vitals

Metric Prod Desktop Prod Mobile Preview Desktop Preview Mobile
LCP 3.4 s 3.2 s 3.8 s 3.0 s
CLS 0.04 0 0 0
FCP 1.2 s 1.2 s 1.2 s 1.2 s
TBT 0 ms 0 ms 0 ms 0 ms
Max Potential FID 50 ms 50 ms 40 ms 40 ms
Time to Interactive 3.4 s 3.2 s 3.8 s 3.0 s

Full Unlighthouse report →

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants