Skip to content

Commit 74ff1cf

Browse files
PAPI-3173 - Introduce private token authentication and deprecate simple token for S2S requests (#1245)
Jira: [PAPI-3173](https://bigcommercecloud.atlassian.net/browse/PAPI-3173) ## What changed? - Introduce private tokens for server-to-server GraphQL Storefront API use and document create/revoke REST endpoints. - Deprecate storefront tokens for server-to-server: new storefront tokens will no longer work statelessly in server-to-server contexts after a future date; recommend private tokens for new s2s integrations. - Stop recommending storefront tokens for s2s: direct server-to-server and headless/server-side flows to private tokens (and customer access tokens where needed). - Clarify storefront tokens as browser-only (CORS via allowed_cors_origins) and allow customer access tokens to be used with either a storefront or private token. - Update/correct GraphQL storefront API token examples to reflect correct JSON structure by nesting the token under a "data" key. - Document private token access scopes (Unauthenticated, Customer, B2B), scope enforcement errors (INSUFFICIENT_ACCESS_SCOPE), and the principle of least privilege. ## Release notes draft - Private tokens are now available for authenticating server-to-server requests to the GraphQL Storefront API. Use them for backend and headless integrations instead of storefront tokens. - Storefront tokens remain for browser-based storefronts; new storefront tokens will stop working for server-to-server after a future date, so use private tokens for new server-to-server integrations. [Learn more](graphql-storefront#private-tokens). ## Anything else? Related to: bigcommerce/developer-center#1346 [PAPI-3173]: https://bigcommercecloud.atlassian.net/browse/PAPI-3173?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent 2bef301 commit 74ff1cf

File tree

5 files changed

+213
-25
lines changed

5 files changed

+213
-25
lines changed

docs/start/authentication/graphql-storefront.mdx

Lines changed: 111 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Authenticating requests to the GraphQL Storefront API
22

3-
Authenticate GraphQL Storefront API requests using bearer tokens passed with the `Authorization` header. You can authenticate using two different kinds of tokens: [storefront tokens](#storefront-tokens) or [customer impersonation tokens](#customer-impersonation-tokens).
3+
Authenticate GraphQL Storefront API requests using bearer tokens passed with the `Authorization` header. You can authenticate using three different kinds of tokens: [storefront tokens](#storefront-tokens), [private tokens](#private-tokens), or [customer impersonation tokens](#customer-impersonation-tokens).
44

55
```http filename="Example request configuration" showLineNumbers copy
66
POST https://your_store.example.com/graphql
@@ -15,15 +15,19 @@ Content-Type: application/json
1515

1616
## Storefront tokens
1717

18-
Storefront tokens are most appropriate to use directly from the web browser, but you can use them in server-to-server communications. If you're creating a token for an application that will make server-to-server or proxied requests to the GraphQL Storefront API, or you work with customer data, use a [customer impersonation token](#customer-impersonation-tokens). If you only wish to query information from an anonymous shopper's perspective, use a storefront token.
18+
Storefront tokens are designed for use directly from the web browser. They support CORS via `allowed_cors_origins` and are intended for browser-based applications. For server-to-server integrations, use [private tokens](#private-tokens) instead. If you need to work with customer data, use a [customer impersonation token](#customer-impersonation-tokens). If you only wish to query information from an anonymous shopper's perspective in a browser context, use a storefront token.
19+
20+
<Callout type="warning">
21+
**Deprecation notice (storefront tokens and server-to-server):** Storefront tokens created **after June 30th, 2026** will no longer support server-to-server (s2s) use. Storefront tokens created **on or before June 30th, 2026** will continue to support s2s calls until **March 31st, 2027**, after which s2s will no longer be supported for those tokens. Use [private tokens](#private-tokens) for server-to-server integrations.
22+
</Callout>
1923

2024
### Storefront token security
2125

2226
Generally speaking, vanilla storefront tokens are not considered sensitive, and it is safe to expose them in web browsers. Storefront tokens can only expose information and actions that shoppers can access when they browse a storefront.
2327

2428
It is possible to create a long-lived token that does not expire. For greater security, we recommend creating shorter-lived tokens and rotating them periodically.
2529

26-
For security reasons, GraphQL Storefront API tokens are scoped to particular [CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) origins, so you must supply the origin or origins on which you intend to use the token. If you have more than two origins, you will need multiple tokens. If you do not supply any CORS origins, the API will reject requests originating from web browsers, although you can still use it in other contexts. GraphQL server-to-server requests do not require `allowed_cors_origins`.
30+
For security reasons, GraphQL Storefront API tokens are scoped to particular [CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) origins, so you must supply the origin or origins on which you intend to use the token. If you have more than two origins, you will need multiple tokens.
2731

2832
### Create a storefront token
2933

@@ -57,7 +61,9 @@ content-type: application/json
5761

5862
```json filename="Example response: Create a storefront API token" showLineNumbers copy
5963
{
60-
"token": "...eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiJ9...",
64+
"data": {
65+
"token": "...eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiJ9..."
66+
},
6167
"meta": {
6268
// ...
6369
}
@@ -68,7 +74,7 @@ content-type: application/json
6874

6975
#### Customer access tokens
7076

71-
A customer access token is unique to an individual user's account because it represents an authenticated storefront session for GraphQL requests. You can obtain and use a customer access token only for server-to-server requests. Therefore, you must use the customer access token with a regular storefront token. A customer access token becomes invalid on all devices when you log out of a single device.
77+
A customer access token is unique to an individual user's account because it represents an authenticated storefront session for GraphQL requests. You can obtain and use a customer access token only for server-to-server requests. Therefore, you must use the customer access token with a storefront token or private token. A customer access token becomes invalid on all devices when you log out of a single device.
7278

7379
<Callout type="warning">
7480
Do not use this token for browser-side or client-side requests.
@@ -86,7 +92,7 @@ There are two options to obtain a customer access token.
8692
Enter your user email and password to use the login mutation. When using the login mutation in a server-to-server context, the mutation will return a customer access token in response to login actions as part of the GraphQL body instead of a cookie header. From there, you can store the customer access token in the presentation layer's session management system and send it with future GraphQL requests. If the login mutation request is from a browser, we will not return the customer access token in the body, and will instead set a cookie.
8793

8894
<Callout type="info">
89-
* Use the [Create a Token](/docs/rest-authentication/tokens#create-a-token) endpoint to generate the storefront bearer token needed to run the login mutation call.
95+
* Use the [Create a Token](/docs/rest-authentication/tokens#create-a-token) endpoint to generate the storefront token or private token needed to run the login mutation call.
9096
* If you request a customer access token in wrong communication context, you will receive the following error:
9197
***Customer access token was requested in the body, but it's only returned for server-to-server requests. For browser requests it's set as an httpOnly cookie instead.***
9298

@@ -280,6 +286,99 @@ query CustomerAttributes {
280286

281287
On Stencil storefronts, you can access a token at render time and pass the token to client-side code using the `{{settings.storefront_api.token}}` Handlebars property. This auto-generated token has an expiry period of 24-48 hours and will rotate before expiration.
282288

289+
## Private tokens
290+
291+
Private tokens are designed for server-to-server integrations. They are always stateless (no session required) and provide better performance for server-to-server use cases.
292+
293+
### Private token characteristics
294+
295+
- **Server-to-server only:** The API rejects private token-authenticated requests that originate from web browsers
296+
- **Always stateless:** No session or cookie validation required
297+
- **Required for server-to-server:** Private tokens are required for server-to-server integrations without customer impersonation. Storefront tokens cannot be used statelessly in server-to-server contexts.
298+
- **Access scoping:** Only private tokens support access scopes (other token types do not). See [Private token access scopes](#private-token-access-scopes) below.
299+
300+
<Callout type="info">
301+
Private tokens are the recommended choice for new server-to-server integrations that don't require customer impersonation. They provide better performance and are designed specifically for stateless server-to-server use cases.
302+
</Callout>
303+
304+
### Private token access scopes
305+
306+
Only **private tokens** use access scopes; storefront and customer access tokens do not. Scopes restrict which GraphQL fields the token can access. The token must include **all** scopes required by every field in the selection (including nested fields).
307+
308+
**Scopes are independent:** There is no hierarchy or inheritance. For example, `Customer` does not include or imply `Unauthenticated`—they cover different areas. You need more than one scope only when your query selects fields that require different scopes (e.g. `checkout { order { id } }` requires both Unauthenticated for `checkout` and Customer for `order`).
309+
310+
**Scope identifiers** (use these in the create request):
311+
312+
| Scope | Use |
313+
|-------|-----|
314+
| `Unauthenticated` | Store, channel, catalog, products, content, cart, checkout (except nested `order`), wishlist create/public, analytics, payment, newsletter. |
315+
| `Customer` | Customer identity, orders, login/logout, session sync, registration (`registerCustomer`), customer wishlists, cart assignment, order messages. |
316+
| `B2B` | Company operations (e.g. `registerCompany`). |
317+
318+
Scopes are required when creating a private token. The API rejects requests with no scopes. Apply the [principle of least privilege](https://en.wikipedia.org/wiki/Principle_of_least_privilege)—request only the scopes you need.
319+
320+
When the token lacks a scope required by a field, the API returns a GraphQL response with `errors`. Affected fields may be `null` in `data`. The error includes `extensions.code: "INSUFFICIENT_ACCESS_SCOPE"` and `extensions.requiredScopes` (the scope identifiers required by that field):
321+
322+
```json filename="Example: insufficient access scope error" showLineNumbers copy
323+
{
324+
"data": null,
325+
"errors": [
326+
{
327+
"message": "The provided token does not include the scopes required to resolve this field.",
328+
"path": ["company"],
329+
"extensions": {
330+
"code": "INSUFFICIENT_ACCESS_SCOPE",
331+
"requiredScopes": ["B2B"]
332+
}
333+
}
334+
]
335+
}
336+
```
337+
338+
### Create a private token
339+
340+
You **must** specify `scopes` when creating a private token. Use the [Create a private token](/docs/rest-authentication/tokens#create-a-private-api-token) REST endpoint to create private tokens. Add the [storefront API tokens creation scope](/docs/start/authentication/api-accounts#token-creation-scopes) to the [store-level or app-level API account](/docs/start/authentication/api-accounts) you use to generate tokens.
341+
342+
<Tabs items={['Request', 'Response']}>
343+
<Tab>
344+
345+
```http filename="Example request: Create a private token" showLineNumbers copy
346+
POST https://api.bigcommerce.com/stores/{{STORE_HASH}}/v3/storefront/api-token-private
347+
x-auth-token: {{access_token}}
348+
accept: application/json
349+
content-type: application/json
350+
351+
{
352+
"expires_at": 1602288000, // when the token will expire, as an integer unix timestamp (in seconds)
353+
"channel_ids": [1, 2, 3], // array of integers (must be valid channel IDs on the store)
354+
"scopes": [ // access scope identifiers (required)
355+
"Unauthenticated",
356+
"Customer",
357+
"B2B"
358+
]
359+
}
360+
```
361+
</Tab>
362+
<Tab>
363+
364+
```json filename="Example response: Create a private token" showLineNumbers copy
365+
{
366+
"data": {
367+
"token": "...eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiJ9..."
368+
},
369+
"meta": {
370+
// ...
371+
}
372+
}
373+
```
374+
</Tab>
375+
</Tabs>
376+
377+
### Private token security
378+
379+
Private tokens are sensitive and should **never** be exposed publicly. Treat them with the same care as other application secrets, such as API account access tokens. Store them securely on your server and never include them in client-side code or expose them in browser contexts.
380+
381+
If your token is compromised, you can use the [Revoke a token](/docs/rest-authentication/tokens#revoke-a-token) endpoint. Only use this in emergencies; do not revoke tokens unnecessarily. Instead, use a shorter expiration and allow them to expire naturally.
283382

284383
## Customer impersonation tokens
285384

@@ -325,11 +424,12 @@ Content-Type: application/json
325424

326425
```json filename="Example response: Create a customer impersonation token" showLineNumbers copy
327426
{
328-
"data":
329-
{
330-
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c"
427+
"data": {
428+
"token": "...eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiJ9..."
331429
},
332-
"meta": {}
430+
"meta": {
431+
// ...
432+
}
333433
}
334434
```
335435
</Tab>
@@ -466,6 +566,7 @@ If you're signed out, the system reverts to a "guest" version of the cart, allow
466566
### Endpoints
467567

468568
* [Create a storefront token](/docs/rest-authentication/tokens#create-a-token)
569+
* [Create a private token](/docs/rest-authentication/tokens#create-a-private-api-token)
469570
* [Create a customer impersonation token](/docs/rest-authentication/tokens/customer-impersonation-token)
470571
* [Revoke a token](/docs/rest-authentication/tokens#revoke-a-token)
471572
* [Customer Login API](/docs/rest-authentication/customer-login)

docs/storefront/graphql/index.mdx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -691,12 +691,16 @@ Use a normal GraphQL Storefront API token. You can use an anonymous `fetch` or `
691691

692692
### I want to run requests from a server, and I don't need customer impersonation abilities
693693

694-
Use normal GraphQL Storefront API tokens. According to the [Principle of least privilege](https://en.wikipedia.org/wiki/Principle_of_least_privilege), you should not create a token that has permissions you do not need.
694+
For server-to-server integrations, you must use [private tokens](/docs/start/authentication/graphql-storefront#private-tokens). Private tokens are designed specifically for stateless server-to-server use cases and provide better performance. According to the [Principle of least privilege](https://en.wikipedia.org/wiki/Principle_of_least_privilege), you should not create a token that has permissions you do not need.
695+
696+
<Callout type="warning">
697+
**Deprecation notice (storefront tokens and server-to-server):** Storefront tokens created **after June 30th, 2026** will no longer support server-to-server (s2s) use. Storefront tokens created **on or before June 30th, 2026** will continue to support s2s calls until **March 31st, 2027**, after which s2s will no longer be supported for those tokens. Use [private tokens](/docs/start/authentication/graphql-storefront#private-tokens) for server-to-server integrations.
698+
</Callout>
695699

696700

697701
### I want to run requests from a server, and I need to support customer login
698702

699-
The recommended option is to use a regular storefront token with a customer access token. You can exchange customer credentials for a customer access token using the login mutation and send the token on future requests using the `X-Bc-Customer-Access-Token` header. The other possible but least preferred option is to use a customer impersonation token and store it securely on your server like other secrets. When you need to run requests in the context of a particular customer (for example, if they've logged in to your application), send their BigCommerce Customer ID along with the request as the `X-Bc-Customer-Id` header. This option provides less security but is available to use.
703+
The recommended option is to use a [private token](/docs/start/authentication/graphql-storefront#private-tokens) with a customer access token. You can exchange customer credentials for a customer access token using the login mutation and send the token on future requests using the `X-Bc-Customer-Access-Token` header. The other possible but least preferred option is to use a customer impersonation token and store it securely on your server like other secrets. When you need to run requests in the context of a particular customer (for example, if they've logged in to your application), send their BigCommerce Customer ID along with the request as the `X-Bc-Customer-Id` header. This option provides less security but is available to use.
700704

701705
### I want a list of GraphQL error messages
702706

docs/storefront/headless/channels.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ Optionally, you can add a 3P SSL certificate for the checkout domain by sending
4141

4242
### Create tokens for the GraphQL Storefront API
4343

44-
After setting up the channel, you're almost ready to authenticate cross-origin requests to the GraphQL Storefront API. You can [Create customer impersonation tokens](/docs/rest-authentication/tokens/customer-impersonation-token#create-a-token) for most headless or server-to-server interactions, or [Storefront tokens](/docs/rest-authentication/tokens#create-a-token) for static frontend site interactions. Use your new channel ID and, where required, supply your channel site as an allowed_cors_origin; otherwise, your requests will be rejected.
44+
After setting up the channel, you're almost ready to authenticate cross-origin requests to the GraphQL Storefront API. For server-to-server interactions, use [private tokens](/docs/rest-authentication/tokens#create-a-private-api-token). For customer impersonation in server-to-server contexts, use [customer impersonation tokens](/docs/rest-authentication/tokens/customer-impersonation-token#create-a-token). For browser-based applications, use [Storefront tokens](/docs/rest-authentication/tokens#create-a-token). Use your new channel ID and, where required, supply your channel site as an allowed_cors_origin for storefront tokens; otherwise, your requests will be rejected.
4545

4646
After you have a token, you're ready to get started using the [GraphQL Storefront API](/docs/storefront/graphql).
4747

docs/storefront/headless/customers.mdx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,15 @@ When you sign in a customer using the login mutation, subsequent queries to the
1919

2020
### Headless and server-side sign-in
2121

22-
To make queries from the perspective of a particular customer using headless or server-side code, use [customer access tokens](/docs/start/authentication/graphql-storefront#customer-access-tokens) and [storefront tokens](/docs/start/authentication/graphql-storefront#storefront-tokens). Then, use the customer access token in the `X-Bc-Customer-Access-Token` header.
22+
To make queries from the perspective of a particular customer using headless or server-side code, use [customer access tokens](/docs/start/authentication/graphql-storefront#customer-access-tokens) with [private tokens](/docs/start/authentication/graphql-storefront#private-tokens). Then, use the customer access token in the `X-Bc-Customer-Access-Token` header.
2323

2424
We recommend using the login mutation and customer access token because this combination is more secure. In addition, there is a seamless redirection and synchronization between headless storefronts and hosted checkouts, which allow for transferring session details, such as customer and cart data, across various contexts.
2525

2626
## Customer single sign-on
2727

2828
If using the customer access token, then you just need to use the `createCartRedirectUrls` mutation and use the redirectUrl provided there. It will be a session sync link that will copy data from the headless storefront to the BigCommerce-hosted page. This approach simplifies session synchronization and offers consistent login states.
2929

30-
```js "Generate redirectUrl example" showLineNumbers copy
30+
```graphql "Generate redirectUrl example" showLineNumbers copy
3131
mutation createRedirectUrl($input: CreateCartRedirectUrlsInput!) {
3232
cart {
3333
createCartRedirectUrls(input: $input) {
@@ -43,13 +43,13 @@ The other option is when a customer signs in to your headless storefront and you
4343

4444
You can sign a customer in to an embedded checkout by using the session-sync URL from the [createCartRedirectUrls mutation](https://developer.bigcommerce.com/graphql-storefront/reference#definition-CartMutations).
4545

46-
```js filename="Example JWT payload" showLineNumbers copy
46+
```json filename="Example JWT payload" showLineNumbers copy
4747
{
48-
"iss": {{CLIENT_ID}},
48+
"iss": "{{CLIENT_ID}}",
4949
"iat": 1535393113,
50-
"jti": {{UUID}},
50+
"jti": "{{UUID}}",
5151
"operation": "customer_login",
52-
"store_hash": {{STORE_HASH}},
52+
"store_hash": "{{STORE_HASH}}",
5353
"customer_id": {{CUSTOMER_ID}},
5454
"channel_id": {{CHANNEL_ID}},
5555
"redirect_to": "/cart.php?embedded=1&action=loadInCheckout&id=bc218c65-7a32-4ab7-8082-68730c074d02&token=aa958e2b7922035bf3339215d95d145ebd9193deb36ae847caa780aa2e003e4b",

0 commit comments

Comments
 (0)