Beta

Create a tenant

A new tenant starts with its own users, OAuth settings, audit history and logs. You are its first Tenant Admin.

BTL Admin

Token formats and validation

The printer's request reaches the photo API with a token in its Authorization header. Before the API can decide whether the printer may see album 42, it has more basic questions to answer. Did the photo service's authorization server issue this token? Was it issued for this API? Is it still valid?

How the API answers depends on what the token contains. Some tokens carry nothing but a reference. Others carry their own description, signed by the authorization server.

Reference and self-contained tokens

A reference token, often called an opaque token, is a random string that points to a record kept by the authorization server. demo-access-token-7 could be one, and only the authorization server knows what it stands for. An API that receives one has to ask the authorization server what it means, which the Token introspection lesson covers.

A self-contained token carries that information itself. The common format is a signed JWT. The authorization server writes the claims and signs them with its private key, and any API holding the matching public key can check the token without contacting anyone.

Neither format is better everywhere:

ConsiderationReference tokenSelf-contained JWT
SizeA short random string.Hundreds of characters or more, sent with every request. The example below is 423.
Who can read itOnly the authorization server knows what it means.Anyone holding it can decode the claims, including the client and anything that logs it.
RevocationTakes effect at the API's next check with the authorization server.An API that validates locally accepts the token until it expires.
Dependence on the authorization serverEvery check needs the authorization server to answer, unless the API caches the result.The API needs only the issuer's public keys, so it keeps working through a short outage.

The privacy row is easy to overlook. A JWT carrying your email address would hand it to every client that received the token, so the authorization server chooses what goes into a self-contained token knowing who can read it.

The JWT access token profile

JWT access tokens follow a standard profile, so APIs and authorization servers agree on what each claim means. If the photo service issued JWTs, the printer's token, shown in earlier lessons as the stand-in demo-access-token-7, would look like this, shortened for display. All domains, keys, and tokens in these examples are fictional.

eyJhbGciOiJFUzI1NiIsImtpZCI6InBob3Rvcy0yMDI2LTA5IiwidHlwIjoiYXQrand0In0
.eyJpc3MiOiJodHRwczovL2F1dGgucGhvdG9zLmV4YW1wbGUiLCJzdWIiOiJ1c2VyLTIwNDgi...
.(signature)

The line breaks are for display. Decoded, its header and claims read:

Header:
{
  "alg": "ES256",
  "kid": "photos-2026-09",
  "typ": "at+jwt"
}

Claims:
{
  "iss": "https://auth.photos.example",
  "sub": "user-2048",
  "aud": "https://api.photos.example",
  "client_id": "photo-printer",
  "scope": "photos.read",
  "iat": 1790845200,
  "exp": 1790845800,
  "jti": "demo-token-id-7"
}

In the header, typ declares that this JWT is an access token under the profile, alg names the signing algorithm, and kid names which of the photo service's keys signed it.

The claims describe the access. iss is the authorization server's issuer identifier. sub is the subject, here your photo account, user-2048; in a token a client obtained for itself with the client credentials grant, it normally identifies the client instead. aud, the audience, names the API the token is for, and client_id the client it was issued to. iat and exp are 09:00 and 09:10 UTC on 1 October 2026, and jti gives this token a unique identifier.

The profile requires every one of those claims except scope, which a server should include whenever the client requested one.

Validating step by step

The Protecting credentials and messages lesson explained that reading a token is not validating it. The photo API works through these checks, in this order, before it trusts any claim:

  1. Accept only the expected algorithm. The API is configured to accept ES256, the algorithm the photo service uses for access tokens, and the token's alg must match; the header never chooses. A token whose alg is none, meaning unsigned, is rejected outright. Letting the header decide has caused real attacks: told by a token to use HMAC, a shared-secret algorithm, some libraries used the server's public key as the secret, and anyone can read a public key.
  2. Check the type. typ must be at+jwt, or its long form application/at+jwt. The same authorization server signs other JWTs too, such as the ID tokens OpenID Connect gives clients after sign-in, and publishes their keys in the same key set. The photo service happens to sign ID tokens with a different algorithm, but nothing requires that, so only this check reliably keeps another kind of JWT from passing as an access token when its other claims happen to fit.
  3. Find the key. The API looks up the token's kid in the photo service's key set at https://auth.photos.example/jwks, an address from its own configuration or the issuer's metadata. It ignores any key the token tries to supply, whether embedded with jwk or linked with jku or x5u. A signature that verifies with the token's own key proves only that its author holds the matching private key, and anyone can make a key pair.
  4. Verify the signature with that key and the expected algorithm.
  5. Check the issuer. iss must equal https://auth.photos.example exactly, with no adjustments for case or a trailing slash. An API that trusts several issuers must also confirm that the key it used belongs to the issuer the token names.
  6. Check the audience. aud may be a single value or a list, and one of its values must be the photo API's own identifier, https://api.photos.example. A genuine token issued for another API is still rejected.
  7. Check the time. The current time must be before exp, and not before nbf ("not before") when the token has one. A small tolerance absorbs differences between the two servers' clocks, which the profile describes as usually no more than a few minutes.

If any check fails, the API answers 401 with invalid_token, and the request goes no further. In practice, these checks are settings for a well-maintained JWT library rather than code to write by hand, and each needs setting explicitly, because a library left on its defaults may skip some.

A token that passes has proved who issued it, which API it was for, and that it is current. It has not yet been compared with what the printer is asking to do, which comes next: first the scope, then the album.

Keeping up with keys

Fetching the key set for every request would make the authorization server part of every API call, so the API caches it and follows the two rules from the Storing and using keys lesson: fetch the set again when a token names an unfamiliar kid, and limit how often that can happen. If the identifier is still unknown after a fresh fetch, the token is rejected.

Those rules let the photo service rotate its access token signing key without coordinating with each API. Before it signs anything with its next key, it publishes that key beside the current one:

GET /jwks HTTP/1.1
Host: auth.photos.example

HTTP/1.1 200 OK
Content-Type: application/json

{
  "keys": [
    { "kid": "photos-2026-09", "kty": "EC", "crv": "P-256", "alg": "ES256", "use": "sig", "x": "...", "y": "..." },
    { "kid": "photos-2027-01", "kty": "EC", "crv": "P-256", "alg": "ES256", "use": "sig", "x": "...", "y": "..." },
    { "kid": "photos-rs-2026-09", "kty": "RSA", "alg": "RS256", "use": "sig", "n": "...", "e": "AQAB" }
  ]
}

The key values are shortened here. The third key, photos-rs-2026-09, signs ID tokens and is not part of this rotation. An API that refreshed its cache after the new key appeared already knows photos-2027-01. One that has not sees an unfamiliar identifier on the first new token, fetches the set again, and finds it. The old key stays published until the last token it signed has expired, ten minutes after the switch with these tokens.

The cache has a cost in the other direction. If a signing key leaks, the photo service removes it from the set immediately, but an API keeps trusting its cached copy until its next scheduled refresh. The unfamiliar-identifier rule never fires, because forged tokens name a key the API already knows. The cache lifetime therefore decides how long a withdrawn key keeps working at that API, and a refresh measured in minutes or an hour, rather than days, keeps the window short.

Try it in the Lab

PUT IT INTO PRACTICE

Check your understanding

Try these questions before moving on. If an answer isn't right, use the feedback and try again.

0 of 2 answered correctly

Enable JavaScript to answer these questions and save progress in this browser.

QUESTION 1 OF 2A JWT reaches the photo API. Its signature verifies with one of the photo service's keys, but its aud names a different API. What should the photo API do?

QUESTION 2 OF 2A token's header contains a jwk parameter with a public key, and the signature verifies with that key. What does that prove?

We value your privacy

We use cookies and similar technologies to enhance your browsing experience, and analytics to understand our traffic. By clicking "Allow All", you consent to optional analytics. Cookie Policy

Learn identity