awesome-everything RU
↑ Back to the climb

Security

ID token validation and JWKS cache management

Crux The eight mandatory checks every OIDC client must perform on id_tokens, why JWKS cache staleness causes auth outages, and the on-cache-miss refresh pattern.
Your altitude — climbing toward senior
ZeroJuniorMiddleSenior
You are at middle altitude — in the sky
◷ 9 min

An IdP rotates its signing key. Your app’s JWKS cache has a 6-hour TTL. For the next 4 hours, every new user gets a 401 — their id_token is signed with the new key, which your cache does not know about. Signature validation fails on a token that is completely valid.

The eight mandatory id_token checks

When your app receives an id_token from an OIDC authorization server, it must validate all eight of the following before trusting the user’s identity. Skipping any one is a documented CVE category.

1. Parse the JWT. Split header.payload.signature. Base64url-decode each part.

2. Read kid from the JWT header. The kid (Key ID) field names which public key signed this token. Without it you cannot look up the right key.

3. Fetch the public key from JWKS. Call the IdP’s /.well-known/jwks.json endpoint (cached, TTL 5–15 min). Find the key whose kid matches. On cache miss, refresh immediately before failing.

4. Verify the signature. Use the algorithm declared in the header (must be RS256 or ES256 — never accept HS256 with an asymmetric key, never accept alg: none). Signature failure → reject. Pin the algorithm server-side; never trust the token’s own alg claim.

5. Validate iss. Must exactly match the expected issuer URL from your configuration. An attacker could issue a token from their own IdP with a valid signature.

6. Validate aud. Must contain your client_id. Without this check, a token issued for a different application is accepted by yours.

7. Validate exp and iat. exp must be in the future; iat must be in the past. Allow ~5 minutes of clock skew (300 seconds is the typical tolerance). Both checks use the server’s clock, not the client’s.

8. Validate nonce. Must match the nonce the client sent in the /authorize request. Without this check, a captured id_token can be replayed into a different session.

id_token validation sequence
1
Parse JWTSplit header.payload.signature
2
kid lookupRead key ID from header → find key in JWKS
3
SignatureVerify with public key; reject alg=none or wrong type
4
issMust match expected issuer (exact string)
5
audMust contain YOUR client_id
6
exp / iatexp in future, iat in past — 300s clock skew allowed
7
nonceMust match nonce sent in /authorize

JWKS cache staleness: the production failure mode

The IdP’s JWKS endpoint lists its current signing public keys. Your app caches this list (fetching it on every request would be too slow). When the IdP rotates signing keys, it publishes the new key — but your cache still has the old one.

What breaks: new tokens are signed with the new key. Your validator reads the kid from the token header, looks in the cache — not found. Signature validation fails. Users cannot log in, even though their tokens are completely valid.

The fix — on-cache-miss refresh: When a kid is not found in the cache, immediately fetch fresh JWKS from /.well-known/jwks.json and retry the lookup. RFC 7517 explicitly allows this. Only fail if the kid is still absent after the refresh.

Long-term mitigation: Short cache TTL (5–15 minutes) plus on-miss refresh as a backstop. Mature IdPs (Auth0, Okta) publish new keys 24–48 hours before activation so clients can pre-warm caches. If your IdP does not, short TTL + on-miss refresh is the only reliable defense.

Quiz

An app validates id_token signature and iss correctly but skips the aud check. What attack is enabled?

Quiz

Why should you never trust the 'alg' claim in the JWT header?

Order the steps

Put the id_token validation steps in the correct order:

  1. 1 Parse the JWT — split header.payload.signature
  2. 2 Read the kid (key ID) from the JWT header
  3. 3 Fetch the matching public key from the issuer's JWKS endpoint (cached, refresh on miss)
  4. 4 Verify the signature using the public key and the pinned expected algorithm
  5. 5 Validate the iss claim matches the expected issuer
  6. 6 Validate the aud claim contains the application's client_id
  7. 7 Validate exp is in the future and iat is in the past (allow ~300s clock skew)
  8. 8 Validate the nonce claim matches the nonce sent in /authorize
Recall before you leave
  1. 01
    What is the on-cache-miss JWKS refresh pattern, and why is it required?
  2. 02
    Why must the aud claim contain YOUR client_id, not just any client_id?
  3. 03
    An app skips the exp/iat check for performance. What is the blast radius of a stolen id_token?
Recap

OIDC id_token validation has eight mandatory steps. Signature verification requires the correct public key from JWKS, selected by kid, with a server-side pinned algorithm — never one read from the token. The iss and aud claims prevent cross-issuer and cross-audience attacks. The exp/iat checks enforce freshness. The nonce check prevents replay. JWKS cache staleness during key rotation is the most common production failure: on-cache-miss refresh plus a short TTL (5–15 minutes) is the standard mitigation.

Connected lessons
appears again in178
Continue the climb ↑Refresh token rotation and scope-based least privilege
shortcuts expand
search
K
prev piece
k
next piece
j
cycle tier
t
this menu
?
sources3
expand
  1. 01
  2. 02
  3. 03

Trademarks belong to their respective owners. Editorial reference only.