OpenID Connect, JWTs and Key Rotation
You'll learn to
- -Tell an ID token from an access token and say who each one is for
- -List the checks required to validate a JWT, including the algorithm allow list
- -Explain discovery and JWKS, and how the kid header lets signing keys rotate without an outage
- -Choose between self-contained JWT access tokens and opaque tokens
OAuth2 answers what an app may do on a user's behalf. It does not say who the user is. 'Sign in with ...' buttons need that second answer, and OpenID Connect (OIDC) adds it on top of OAuth2 with an ID token and a few standard endpoints. The token format underneath is usually a JWT, and most real incidents come from validating one carelessly.
An Analogy: Passport and Boarding Pass
A passport tells the airline who you are. A boarding pass tells the gate agent what you may do: board this flight, in this seat. You would not hand a gate agent your passport as a boarding pass or the reverse. The ID token is the passport, for the app to learn who signed in. The access token is the boarding pass, for the API to decide what is allowed.
ID Token vs Access Token
- -ID token: a JWT for the client app. Its audience (aud) is the app's client id, and it holds claims such as sub (the user's stable id), iss, exp and the nonce the app sent. The app reads it to learn who logged in.
- -Access token: for the API (the resource server). Its audience is the API. It may be a JWT or an opaque string. The API reads its scopes and subject to authorize the call.
An API must never accept an ID token as an access token. They have different audiences, and treating one as the other is a classic confusion bug: a token minted for some other app could be replayed against your API.
What a JWT Is
A JSON Web Token is three base64url parts joined by dots: a header (which algorithm and which key id, kid), a payload of claims, and a signature over the first two parts. The signature proves the token was not changed and who issued it. It does not hide anything: anyone can decode the payload, so never put secrets in it. The block below builds a signed token and then attacks it in four ways. It uses a shared secret for brevity; real OIDC providers sign with a private key, and the API verifies with the public key. Press Run.
Only the first token is accepted. Changing the subject to admin breaks the signature. A token signed correctly but meant for the billing API fails the audience check, an expired one fails the clock check, and alg: none, a header that asks the verifier to skip the signature, is refused because the server decides which algorithms it accepts, never the token.
The Validation Checklist
- -Accept only algorithms on your own allow list. Never trust the alg in the token, and never accept none.
- -Verify the signature with the key that the kid names, from the issuer's published key set.
- -Check iss equals the issuer you trust and aud includes your API.
- -Check exp (and nbf if present) against the clock, allowing a small skew of a minute or two.
- -Then check scopes and claims for the action being requested.
Discovery, JWKS and Key Rotation
An OIDC provider publishes a discovery document at /.well-known/openid-configuration listing its endpoints and a jwks_uri. That address serves a JWKS, a set of public keys each labeled with a kid. APIs fetch it, cache it, and pick the key whose kid matches the token's header. If the kid is unknown, they refetch once (rate limited) in case the issuer has just rotated.
Rotation is safe when it overlaps. The issuer first publishes the new public key in the JWKS, then starts signing new tokens with it, and keeps the old public key published until every token signed with it has expired. Verifiers never see a moment when a valid token has no matching key. Removing the old key too early is the usual cause of a sudden wave of 401s.
The pictures show why an API can verify a token without calling the issuer, and what rotation looks like from the verifier's side. Notice that the public keys travel to the API, while the signing key never leaves the issuer.
Each key has a kid. The private signing key stays with the issuer.
It picks the key by the token's kid and checks signature, issuer, audience and expiry.
The old key stays published until its tokens have expired.
JWT or Opaque Token?
A JWT is self-contained, so an API can verify it with no network call, which scales well. The price is that you cannot take it back before it expires, so keep access tokens short (minutes) and keep a denylist only for emergencies. An opaque token is a random string that the API checks by asking the authorization server (token introspection): one extra call, but revocation is instant. Many systems use JWTs at the edge and opaque tokens for the most sensitive operations.
Interview Signal is part of Pro
See a real weak answer next to a real strong one for this exact topic.
Quiz is part of Pro
Test what you just read with a short quiz, and bank the XP.
Sign In With Us in the API Design Lab.
Discussion0
Join the Discussion
Sign in to leave comments, reply to others, or like insights.
No comments yet. Be the first to start the thread!