Skip to main content

JWT Access Token Authentication

Contents

Overview

JWT access token authentication allows end-users (shoppers/customers) to be authenticated against the Spaaza API using a signed JSON Web Token (JWT) issued by a trusted external identity provider. This is an alternative to the standard session-based user authentication and is designed for integrations where the client already holds a JWT access token and does not need to call the Spaaza login endpoint.

This page is intended for developers integrating an external identity provider with the Spaaza API who need to authenticate end-users using JWT access tokens.

For a general overview of the different authentication methods supported by the Spaaza API (including standard user, admin, and privileged authentication), see the Authentication section of the API documentation.

How JWT access token authentication works

The authentication flow is as follows:

  1. The client obtains a JWT access token from a trusted identity provider (e.g. an OAuth 2.0 authorization server or custom identity service).
  2. The client sends the JWT in the X-Spaaza-Access-Token-JWT header along with the X-Spaaza-MyPrice-App-Hostname header to identify the Spaaza app context.
  3. The Spaaza API validates the JWT:
    • Verifies the signature using the public keys published at the chain's configured JWKS (JSON Web Key Set) URL.
    • Checks the token's header fields (alg, kid, typ).
    • Validates the claims (iss, exp, customer_guid, and optionally aud).
  4. If validation succeeds, the API resolves the customer_guid claim to the corresponding Spaaza user and processes the request in the context of that user.

No server-side session is created or stored. Each request is independently verified.

Chain configuration

Before JWT access token authentication can be used, the chain must be configured with the identity provider's trust settings. These fields are returned by the get-chain endpoint:

FieldDescription
jwt_jwks_url(string, required) The HTTPS URL of the identity provider's JWKS (JSON Web Key Set) endpoint. This endpoint publishes the public keys used to verify JWT signatures. Must use the https scheme.
jwt_issuer(string, required) The expected value of the iss (issuer) claim in the JWT. This must match the identity provider's issuer identifier exactly.
jwt_audience(string, optional) The expected value of the aud (audience) claim in the JWT. When configured, the API verifies that the token's audience includes this value. When not configured, audience validation is skipped.

jwt_jwks_url and jwt_issuer must both be set or both be cleared. jwt_audience is independent and can be omitted for identity providers that issue multi-service tokens without a dedicated audience for the Spaaza API.

Required header

To authenticate an end-user with a JWT access token, the following HTTP headers must be sent with each API request:

HeaderDescription
X-Spaaza-Access-Token-JWT(string, required) The signed JWT access token issued by the trusted identity provider.
X-Spaaza-MyPrice-App-Hostname(string, required) The hostname of the Spaaza app that the request is associated with. This determines the chain context.

When the X-Spaaza-Access-Token-JWT header is present, the API uses JWT access token authentication. The standard session-based headers (X-Spaaza-Session-User-Id, X-Spaaza-Session-Key) are not required and are ignored when a JWT access token is provided.

JWT format

The JWT must conform to the RFC 9068 profile for OAuth 2.0 access tokens. It consists of three Base64url-encoded segments separated by dots: <header>.<payload>.<signature>.

Header

The JWT header must contain the following fields:

FieldDescriptionRequired
algThe signing algorithm. Must be RS256.Yes
kidThe key ID identifying which public key in the JWKS was used to sign the token.Yes
typThe token type. Must be at+jwt (access token JWT) when present.No

Example header (after Base64url decoding):

{
"alg": "RS256",
"kid": "key-2026-04",
"typ": "at+jwt"
}

Payload

The JWT payload must contain the following claims:

ClaimDescriptionRequired
issThe issuer identifier. Must exactly match the jwt_issuer value configured on the chain.Yes
expThe expiration time of the token as a Unix timestamp (seconds since epoch). Requests with expired tokens are rejected. A clock skew tolerance of up to 60 seconds is applied.Yes
customer_guidA string that uniquely identifies the end-user in the identity provider's system. This value is mapped to the Spaaza user via their authentication point identifier for the relevant app.Yes
audThe intended audience for the token. Can be a single string or an array of strings. Validated only when jwt_audience is configured on the chain.When jwt_audience is configured
scopeAn array of scope strings or a space-delimited scope string indicating the permissions granted to the token. Used for endpoint-level scope enforcement.No
nbfThe "not before" time as a Unix timestamp. If present, the token is rejected before this time (with clock skew tolerance).No
iatThe "issued at" time as a Unix timestamp.No

Sample JWT

The following is an example of a decoded JWT access token suitable for use with the Spaaza API.

Header:

{
"alg": "RS256",
"kid": "key-2026-04",
"typ": "at+jwt"
}

Payload:

{
"iss": "https://identity.example.com",
"aud": "example-rewards-api",
"exp": 1776865960,
"iat": 1776862360,
"customer_guid": "cust-00412",
"scope": ["customer_data", "customer_profile.read"]
}

In this example:

  • The token was issued by https://identity.example.com and is intended for example-rewards-api.
  • The end-user is identified by customer_guid value cust-00412, which corresponds to their authentication point identifier in Spaaza.
  • The token grants customer_data and customer_profile.read scopes.

Signature verification and JWKS

The Spaaza API verifies the JWT signature using the public key identified by the kid header field. The public key is retrieved from the JWKS (JSON Web Key Set) document published at the chain's configured jwt_jwks_url.

The JWKS document is cached to avoid fetching it on every request. The cache TTL respects the Cache-Control: max-age directive from the JWKS endpoint response when available, and defaults to 3600 seconds (one hour) otherwise.

Key rotation

When the identity provider rotates signing keys, the new key's kid will not initially be present in the cached JWKS. The API handles this automatically:

  1. If the JWT's kid is not found in the cached JWKS, the API fetches a fresh copy of the JWKS from the configured URL.
  2. If the refreshed JWKS contains the kid, signature verification proceeds normally.
  3. If the kid is still not found after a refresh, the request is rejected.

This approach ensures seamless key rotation without requiring manual cache invalidation. During a key rollover period, the identity provider should publish both the old and the new keys in the JWKS document.

Claim validation

After signature verification, the following claim checks are performed:

  1. Issuer (iss): must exactly match the chain's jwt_issuer configuration.
  2. Audience (aud): if jwt_audience is configured on the chain, the token's aud claim (whether a single string or an array) must include the configured value. If jwt_audience is not configured, audience validation is skipped.
  3. Expiration (exp): the token must not be expired. A clock skew tolerance of up to 60 seconds is applied.
  4. Customer GUID (customer_guid): must be a non-empty string that maps to a known Spaaza user via their authentication point identifier for the relevant app.

If any of these checks fail, the request is rejected with an authentication error.

Scope enforcement

JWT access tokens may include a scope claim that indicates the permissions granted to the token. Spaaza uses these scopes for endpoint-level authorization.

When a JWT-authenticated request reaches an endpoint, the API checks that the token includes at least one of the required scopes. If the endpoint does not specify explicit scope requirements, the following baseline scopes are checked:

  • customer_data
  • customer_profile.read
  • customer_profile.write

Some endpoints may enforce specific scopes. For example, endpoints that modify the user profile require the customer_profile.write scope.

If the token does not include any of the required scopes, the request is rejected with an access denied error.

User resolution

The customer_guid claim in the JWT is used to identify the end-user. This value is matched against the authentication point identifier associated with the user's account for the relevant Spaaza app (as determined by the X-Spaaza-MyPrice-App-Hostname header).

If no Spaaza user is found for the given customer_guid and app combination, the request is rejected.

Stateless sessions

JWT access token authentication creates an ephemeral, request-scoped session. Unlike standard user authentication:

  • No session is stored server-side.
  • Each request is independently validated against the JWT.
  • The session cannot be revoked via the logout endpoint. Token expiry is the primary mechanism for session termination.
  • The client is responsible for obtaining fresh tokens from the identity provider as needed.

Error handling

When JWT access token authentication fails, the API returns a generic error message. Detailed failure reasons (such as specific claim mismatches or signature errors) are logged server-side but are not exposed in the API response. This prevents leaking information about the chain's trust configuration to callers.

Common reasons for authentication failure include:

  • The JWT signature cannot be verified against the JWKS.
  • The kid in the JWT header does not match any key in the JWKS.
  • The iss claim does not match the chain's configured issuer.
  • The aud claim does not match the chain's configured audience (when audience validation is enabled).
  • The token has expired.
  • The customer_guid does not correspond to a known Spaaza user for the app.
  • The token is missing required scopes for the requested endpoint.