JWT Key Rotation with JWKS

SecurityJWTAuthenticationOAuth
Share on LinkedIn Share on X Share on Reddit Share on HN Share on Bluesky

Auth0 rotated signing keys at 3 AM and your microservices rejected every token until pods restarted—because they cached one public key forever and ignored kid in the JWT header. JSON Web Key Sets publish the issuer's current verification keys at a URL like /.well-known/jwks.json. Rotation means adding a new key, signing fresh tokens with it, and retaining old public keys until nothing valid still references them. Done right, users notice nothing; done wrong, global logout looks like an outage.

JWT header carries kid

{
  "alg": "RS256",
  "typ": "JWT",
  "kid": "2025-05-key-2"
}

Verifiers select the matching JWK from the set:

{
  "keys": [
    {
      "kty": "RSA",
      "kid": "2025-05-key-1",
      "use": "sig",
      "n": "...",
      "e": "AQAB"
    },
    {
      "kid": "2025-05-key-2",
      "n": "...",
      "e": "AQAB"
    }
  ]
}

Rotation procedure

  1. Generate new key pair; add public JWK with new kid to JWKS
  2. Start signing new tokens with private key #2
  3. Wait max(access_token_ttl) + skew
  4. Remove public key #1 from JWKS
  5. Destroy private key #1 from HSM or secrets store

Automate steps 1–2 on schedule (quarterly) and on compromise immediately.

Verifier caching logic

def verify_jwt(token: str) -> Claims:
    header = decode_header(token)
    key = jwks_cache.get(header["kid"])
    if key is None:
        jwks_cache.refresh(force=True)
        key = jwks_cache.get(header["kid"])
    if key is None:
        raise AuthError("unknown signing key")
    return jwt.decode(token, key, algorithms=["RS256"], audience=AUD)

Set cache TTL below rotation frequency but refresh aggressively on miss. Use Cache-Control from JWKS response when present.

Never trust jku or x5u from tokens

Accept keys only from configured issuer metadata (issuer/.well-known/jwks.json). Attackers embed malicious jku URLs in forged tokens hoping naive libraries fetch them.

HSM and KMS integration

Generate and store private keys in AWS KMS, Google Cloud HSM, or Vault transit engine. Signing happens inside HSM; JWKS publishes only public components. Audit every sign operation.

Refresh tokens and rotation

If refresh tokens are JWTs (often opaque is better), they may outlive access tokens. Prefer opaque refresh tokens stored server-side, or encrypt refresh JWTs with separate long-lived keys rotated on different schedule.

Alert when tokens present unknown kid after cache refresh—possible misconfiguration or attack. Track signing latency during HSM calls.

Never trust jku or x5u from tokens. Accept keys only from configured issuer metadata. Attackers embed malicious jku URLs hoping naive libraries fetch them.

Monitor unknown kid after cache refresh—misconfiguration or attack. HSM signing adds latency—track p99 auth path during rotation events.

Refresh tokens as opaque server-side records simplify rotation versus long-lived JWT refresh tokens. If refresh must be JWT, use separate key material and rotation schedule from access tokens.

Rollout and ownership

Teams shipping this capability should wire observability before calling the work done: metrics on the user-visible outcome the control protects, alerts linked to runbook steps, and at least one automated test covering the last incident class you care about. Slice dashboards by region and device during rollout because global averages hide bad canaries. When vendors, routes, or org structure change, revisit assumptions from launch week—they age faster than code. Document rollback commands in the runbook header so on-call does not rediscover steps during pagination. Cross-functional review after major traffic shifts keeps product, platform, and security aligned on the leading metric.

Rollout and ownership

Teams shipping this capability should wire observability before calling the work done: metrics on the user-visible outcome the control protects, alerts linked to runbook steps, and at least one automated test covering the last incident class you care about. Slice dashboards by region and device during rollout because global averages hide bad canaries. When vendors, routes, or org structure change, revisit assumptions from launch week—they age faster than code. Document rollback commands in the runbook header so on-call does not rediscover steps during pagination. Cross-functional review after major traffic shifts keeps product, platform, and security aligned on the leading metric.

Rollout and ownership

Teams shipping this capability should wire observability before calling the work done: metrics on the user-visible outcome the control protects, alerts linked to runbook steps, and at least one automated test covering the last incident class you care about. Slice dashboards by region and device during rollout because global averages hide bad canaries. When vendors, routes, or org structure change, revisit assumptions from launch week—they age faster than code. Document rollback commands in the runbook header so on-call does not rediscover steps during pagination. Cross-functional review after major traffic shifts keeps product, platform, and security aligned on the leading metric.

Rollout and ownership

Teams shipping this capability should wire observability before calling the work done: metrics on the user-visible outcome the control protects, alerts linked to runbook steps, and at least one automated test covering the last incident class you care about. Slice dashboards by region and device during rollout because global averages hide bad canaries. When vendors, routes, or org structure change, revisit assumptions from launch week—they age faster than code. Document rollback commands in the runbook header so on-call does not rediscover steps during pagination. Cross-functional review after major traffic shifts keeps product, platform, and security aligned on the leading metric.

Rollout and ownership

Teams shipping this capability should wire observability before calling the work done: metrics on the user-visible outcome the control protects, alerts linked to runbook steps, and at least one automated test covering the last incident class you care about. Slice dashboards by region and device during rollout because global averages hide bad canaries. When vendors, routes, or org structure change, revisit assumptions from launch week—they age faster than code. Document rollback commands in the runbook header so on-call does not rediscover steps during pagination. Cross-functional review after major traffic shifts keeps product, platform, and security aligned on the leading metric.

Shipping sec jwt key rotation jwks without regrets

Security work around sec jwt key rotation jwks fails when it is treated as a checklist instead of a feedback loop. Start from a threat model: who is the adversary, what is the asset, and which control fails closed if misconfigured. For sec jwt key rotation jwks, I write the abuse cases first — credential stuffing, dependency CVE, log exfiltration, CSRF on cookie sessions — then map each to a detection signal and an owner.

Controls that actually change outcomes

Control Where enforced Failure mode
Input validation API edge Injection / mass assignment
Authn IdP + resource server Stolen session / token
Authz Policy engine Broken object level auth
Secrets Vault / KMS Long-lived plaintext keys

Wire these into CI where possible. A control that only lives in a wiki page will not survive the next on-call rotation.

Incident-shaped verification

Run a tabletop: assume the primary control for sec jwt key rotation jwks failed at 02:00. Who gets paged? What is the first command? How do you revoke access or roll credentials without cascading outages? If you cannot answer in under five minutes, the design is incomplete.

Measurement

Track mean time to remediate findings related to sec jwt key rotation jwks, false-positive rate of scanners, and number of production changes that bypass the gate. Celebrate burn-down of legacy exceptions with expiry dates — permanent exceptions are just vulnerabilities with paperwork.

Pitfalls specific to this domain

Avoid denylist-only validation, logging secrets "temporarily," and blocking every advisory at severity informational. Prefer allowlists, structured redaction, and severity+reachability for gates. Document the dual-credential or dual-key window whenever rotation is involved so operators do not revoke early.

Resources

Frequently asked questions

How long should overlapping signing keys remain valid?

Keep the previous key in JWKS until all issued access tokens expire plus client clock skew—typically overlap equals max token TTL plus 5–10 minutes. Refresh tokens signed with old keys need longer overlap or forced re-login on rotation. Document overlap window in runbooks so operators do not delete old keys early.

Where should resource servers fetch JWKS?

Fetch from the issuer's well-known JWKS URI over TLS, cache keys by kid with HTTP cache headers respected, and pin issuer URL in config—never accept jku headers pointing at attacker URLs. Refresh cache on unknown kid before failing verification to handle rotation race windows.

RSA or ECDSA for JWT signing?

ECDSA P-256 (ES256) offers smaller tokens and faster verification than RSA2048 with comparable security margins for most APIs. HMAC (HS256) suits single-service monoliths where secret never leaves the issuer; multi-service architectures should use asymmetric keys so only the auth service holds private material.

Hiring a senior Android / Flutter engineer?

I architect and ship production mobile software — Kotlin, Jetpack Compose, Flutter — for robotics, EV infrastructure, fintech, and real-time systems. Open to remote roles in Europe and the US.

Get in touch →