Authentication & tokens
Evolve uses a federated token system to manage authentication across
the GraphQL gateway and backend services. The gateway (Hive Router) is
the single public entry point: its federated_token plugin
(hive-router-federated-token, compiled into the router binary) reads
and validates tokens from cookies, injects them into subgraph requests
as the x-access-token header, and writes updated tokens back to
cookies on the response. Backend services use
@labdigital/federated-token to read and modify the token.
Token types
Section titled “Token types”The system uses three distinct tokens, each with a different purpose and visibility:
| Token | Format | Storage | Lifetime | Readable by frontend |
|---|---|---|---|---|
| Access token | JWE (encrypted JWT) | userToken or guestToken cookie (httpOnly) |
2 days | No |
| Refresh token | JWE (encrypted JWT) | refreshToken cookie (httpOnly, path-restricted) |
200 days (CT inactivity timeout) | No |
| Data token | JWT (signed, not encrypted) | userData or guestData cookie |
90 days | Yes |
Access token: holds the commercetools API access token, its
expiry, the subject (customer_id:<id> or anonymous_id:<id>), and
whether the session is authenticated. Encrypted with AES-256-GCM so
the client cannot read or tamper with it.
Refresh token: holds the commercetools refresh token. Stored in a
separate cookie with a restricted path (/graphql/auth) so it is only
sent on refresh requests, not on every API call.
Data token: holds non-sensitive metadata the frontend needs without a server round-trip. The customer’s first name, last name, and B2B business context. Signed (HS256) so the frontend can read it but cannot modify it.
The frontend also receives non-httpOnly indicator cookies
(userRefreshTokenExists / guestRefreshTokenExists) so JavaScript
can detect whether a refresh token exists without being able to read
the token itself.
Token flow
Section titled “Token flow”sequenceDiagram
actor Browser
participant GW as Gateway
participant Svc as Domain service
participant CT as commercetools
Note over Browser,CT: Authenticated request
Browser->>GW: GraphQL request + cookies
GW->>GW: Decrypt access token (JWE)
GW->>Svc: Forward request + X-Access-Token header
Svc->>CT: API call with CT access token
CT-->>Svc: Response
Svc-->>GW: Response + X-Access-Token (if modified)
GW->>GW: Create new JWE cookies if tokens changed
GW-->>Browser: Response + Set-Cookie headers
The gateway serializes the FederatedToken into a base64-encoded
X-Access-Token header for downstream services. Refresh tokens are
forwarded via X-Refresh-Token. Store context (store key, locale,
currency) travels in separate X-StoreContext-* headers.
When a downstream service modifies the token (e.g., during login), it
returns the updated token in X-Access-Token and X-Refresh-Token
response headers. The gateway merges these changes and writes new
cookies.
The @requiresSession directive
Section titled “The @requiresSession directive”The @requiresSession GraphQL directive ensures a valid session
exists before a field resolves. It supports two modes:
@requiresSession: requires an existing session. Throws aREQUIRES_SESSIONerror if no token is present.@requiresSession(create: true): creates an anonymous session on the fly if no token exists. Used for operations that should work without prior login, such as adding items to a cart.
When create: true fires and no session exists, the subgraph creates
an anonymous commercetools token itself and sets it on the
FederatedToken. The router’s federated_token plugin picks up the
updated token from the response headers and writes the session
cookies.
Fields using @requiresSession(create: true):
cartLineItemsAdd,shoppingListCreate,shoppingListLineItemsAdd,createGuestSession
Fields using @requiresSession (existing session required):
cartUpdate,cartLineItemsUpdate,cartLineItemsRemove,cartDiscountCodeAdd,cartDiscountCodeRemove,checkoutComplete,createPayment,shoppingList,shoppingLists, and other account/order mutations
Guest vs authenticated flows
Section titled “Guest vs authenticated flows”Guest flow
Section titled “Guest flow”- A visitor performs an action that requires a session (e.g., adds an
item to the cart via
cartLineItemsAdd) - The
@requiresSession(create: true)directive fires - The subgraph requests an anonymous token from commercetools OAuth
(
grant_type=client_credentialson the anonymous endpoint) and sets it on the federated token - The gateway creates
guestTokenandguestDatacookies - The original GraphQL operation proceeds with the new anonymous token
- The customer calls the
customerLoginmutation with email and password - If the customer has an existing anonymous session, the account
service calls commercetools’
me().login()endpoint, which automatically merges any anonymous cart into the customer’s cart - A new customer token is obtained via the password grant
- The gateway replaces
guestToken→userTokenandguestData→userDatacookies
Cart merging is handled transparently by commercetools when logging in with an active anonymous session.
Token refresh
Section titled “Token refresh”Access tokens expire after 2 days. The frontend detects expiry through the Next.js middleware:
- On each request, the middleware checks if
userTokenis present and not within 5 minutes of expiry - If the token is expired but
userRefreshTokenExistsis set, the user is redirected to/account/refresh-session - The refresh page calls the
refreshTokenGraphQL mutation via/graphql/auth(the refresh token cookie’s restricted path) - The account service exchanges the refresh token for a new access
token via commercetools OAuth (
grant_type=refresh_token) - On success, the gateway writes a new
userTokencookie - On failure (expired or revoked refresh token), all cookies are cleared and the user is redirected to the login page
The refresh page uses exponential backoff with up to 5 retries before giving up.
Session REST endpoints
Section titled “Session REST endpoints”The account service exposes three REST endpoints for session management:
POST /api/create-session: creates an anonymous commercetools
session. Returns a FederatedToken-wrapped response with
accessToken and refreshToken (base64-encoded). REST alternative to
the createGuestSession mutation.
POST /token/anonymous: creates an anonymous session and returns
raw TokenInfo (subject, accessToken, refreshToken,
expiresAt). Used by server-side consumers that need direct API
access (e.g., the MCP server).
POST /api/client-context: accepts an X-Access-Token header
and returns customer metadata (id, givenName, familyName,
email, customerGroupId, businessContext). Results are cached in
Redis for 300 seconds. Used by services that need customer information
without a full GraphQL query.

