Skip to content

New: AI agent integration via Model Context Protocol (MCP).Learn more

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.

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.

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 GraphQL directive ensures a valid session exists before a field resolves. It supports two modes:

  • @requiresSession: requires an existing session. Throws a REQUIRES_SESSION error 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
  1. A visitor performs an action that requires a session (e.g., adds an item to the cart via cartLineItemsAdd)
  2. The @requiresSession(create: true) directive fires
  3. The subgraph requests an anonymous token from commercetools OAuth (grant_type=client_credentials on the anonymous endpoint) and sets it on the federated token
  4. The gateway creates guestToken and guestData cookies
  5. The original GraphQL operation proceeds with the new anonymous token
  1. The customer calls the customerLogin mutation with email and password
  2. 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
  3. A new customer token is obtained via the password grant
  4. The gateway replaces guestTokenuserToken and guestDatauserData cookies

Cart merging is handled transparently by commercetools when logging in with an active anonymous session.

Access tokens expire after 2 days. The frontend detects expiry through the Next.js middleware:

  1. On each request, the middleware checks if userToken is present and not within 5 minutes of expiry
  2. If the token is expired but userRefreshTokenExists is set, the user is redirected to /account/refresh-session
  3. The refresh page calls the refreshToken GraphQL mutation via /graphql/auth (the refresh token cookie’s restricted path)
  4. The account service exchanges the refresh token for a new access token via commercetools OAuth (grant_type=refresh_token)
  5. On success, the gateway writes a new userToken cookie
  6. 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.

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.