JWKS & key rotation
tryMellon publishes its session-token signing keys at a standards-compliant JWKS endpoint. Your validator caches them, and during rotation the previous and new keys coexist so in-flight tokens stay valid.
Endpoint
GET https://api.trymellonauth.com/.well-known/jwks.json
Cache-Control: public, max-age=3600
Response shape (RFC 7517):
{
"keys": [
{
"kty": "RSA",
"kid": "k_a4f2…",
"use": "sig",
"alg": "RS256",
"n": "…base64url…",
"e": "AQAB"
}
]
}
The kid is the RFC 7638 thumbprint of the key — deterministic, so a key has the same kid across deploys.
Token header references the key
Every JWT issued by tryMellon carries a kid in its header that must match an entry in JWKS:
// Header (decoded)
{ "alg": "RS256", "typ": "JWT", "kid": "k_a4f2…" }
Your validator picks the JWK by kid, then verifies with that key’s public components.
Rotation
sequenceDiagram
participant API as TryMellon
participant V as Your validator (cached JWKS)
participant T as Token holder
Note over API: T+0: rotation initiated
API->>API: Generate new key (k_b9c3…)
API->>API: JWKS = [k_a4f2 (previous), k_b9c3 (current)]
API->>T: New tokens signed with k_b9c3
V->>API: GET /.well-known/jwks.json (cache miss on k_b9c3)
API-->>V: Both keys returned
Note over V: Both old + new tokens verify
Note over API: T+TTL: previous key removed
API->>API: JWKS = [k_b9c3]
T->>V: Old token (k_a4f2) → JWT_KID_MISMATCH
The previous key remains in JWKS for at least the longest valid token TTL (24h by default) so no in-flight token is rejected mid-flight.
Cache strategy
| Approach | Use case |
|---|---|
jose.createRemoteJWKSet (Node) | Default. Refreshes on unknown kid. |
jwk.NewCache (Go jwx) | Default. TTL refresh + on-demand. |
Manual lru_cache(1) (Python) | OK for low-throughput. Must invalidate on JWTError with unknown kid. |
| In-memory cache, no refresh | ❌ — breaks on rotation. |
Rule: if your validator sees a kid not in cache, refresh JWKS once and retry. If still missing, the token is invalid.
Debugging JWT_KID_MISMATCH
# 1. Decode the token header (no verify)
echo "$TOKEN" | cut -d. -f1 | base64 -d | jq .kid
# 2. Compare against current JWKS
curl -s https://api.trymellonauth.com/.well-known/jwks.json | jq '.keys[].kid'
If the token’s kid is missing from JWKS:
- Token was issued before a rotation completed and is now past its lifetime — re-authenticate.
- Token was forged or copied from another environment — reject.
- Your cache is stale — force refresh.