Skip to content
TDACorpIDENTITYSign in

JWKS and key rotation

Fetch the public signing keys, cache them correctly, and keep verifying through a key rotation without a deploy.

On this page

Identity signs ID tokens, access tokens and logout tokens. It publishes the matching public keys as a JSON Web Key Set so that you can verify them yourself. Keys change over time, and your verification has to keep working when they do.

Request#

GET the jwks_uri from discovery, which is /.well-known/jwks.json. No authentication, no parameters.

PropertyValue
Response200, Content-Type: application/jwk-set+json, the registered type for a key set
Cache-Controlpublic, max-age=3600, s-maxage=60
ETagA strong validator over the exact body. It changes when, and only when, the published set changes.
Conditional requestSend If-None-Match with the last ETag. An unchanged set returns 304 with the same validators and no body.
CORSAccess-Control-Allow-Origin: *

Response members#

The body is { "keys": [ ... ] }. Each key is a public JWK and carries these members.

MemberMeaning
kty, crv, and the key's public valuesOKP with Ed25519 for EdDSA, or EC with P-256 for ES256. Never a private member.
usesig
algEdDSA or ES256
kidThe key identifier. It appears in the kid header of every token the key signs.
{  "keys": [    {      "kty": "OKP",      "crv": "Ed25519",      "x": "11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo",      "use": "sig",      "alg": "EdDSA",      "kid": "ex-2026-10"    }  ]}

The values are illustrative. The current signing key is listed first, but the order carries no guarantee.

Errors#

The set is public, so there are no OAuth errors. A fault returns a 5xx problem response. If you have a copy, keep using it and try again later.

How rotation looks to a client#

Identity rotates its signing keys. In a normal rotation, two rules make it safe for a client that behaves.

  • A new key is published before it signs anything. Once it is in the set you can already verify with it.
  • A retired key stays published after it stops signing, for longer than the longest token it could have signed lives (an access token lives at most 15 minutes) plus a day. Tokens signed just before a rotation keep verifying.

The set can hold more than one key at once, and keys for other kinds of signed objects, such as logout tokens, share the same set. So:

  • Select the key by kid from the token's header, never by position, and never by trying every key.
  • Check alg. Use only a key whose alg is on your allow-list, and import it under that same algorithm so the header cannot pick a different one.
  • Refuse a token with no kid.

Caching guidance#

  1. Fetch the set when you first need it and keep it in memory. Honour max-age; an hour is the default.
  2. On a token whose kid you do not hold, fetch the set again once, then decide. A new key is expected after a rotation.
  3. Limit that refetch. A token with a kid that never appears is either forged or from another issuer; do not let it make you hit the endpoint on every request. The SDK refetches at most once per 30 seconds for an unknown kid.
  4. If a refetch fails, keep verifying with the keys you hold. Never accept a token you cannot verify.
  5. Use If-None-Match when you refresh so an unchanged set costs no body.

The SDK does all of this: its relying party keeps the set for ten minutes and refetches on an unknown kid, and createResourceServer reads the same set remotely, binding each key to its algorithm, through a key-set resolver that fetches again when it meets a kid it does not hold.

Worked example#

curl -si https://identity.tdacorp.in/.well-known/jwks.json | head -n 8
HTTP/2 200content-type: application/jwk-set+jsoncache-control: public, max-age=3600, s-maxage=60etag: "9f2c4b…"access-control-allow-origin: *

Next time:

curl -si -H 'If-None-Match: "9f2c4b…"' https://identity.tdacorp.in/.well-known/jwks.json | head -n 1
HTTP/2 304

Security notes#

  • Only trust a key set from the issuer you configured, found through its discovery document. Do not follow a jku or jwk header in a token.
  • Pin the algorithm, not the header. Algorithm confusion happens when a verifier believes the token's alg. The SDK accepts only EdDSA and ES256, further limited to the algorithms the issuer actually publishes a key for, and it refuses any other value in its configuration when you create the client.
  • Do not hard-code a key. A hard-coded key breaks at the next rotation.
  • Keep a clock. A cache past its life should be refreshed; a clock that is wrong makes max-age meaningless.

Verified against commit 5d85077