A JSON Web Token is only as trustworthy as the code that validates it, and most JWT failures come from verifiers that let the token decide how it should be checked. JWTs carry access decisions for APIs, single sign-on, service-to-service calls and workload federation, so one missing check (algorithm, key source, audience, type or expiry) turns a signed token into a forgery path. This guide covers the JOSE structure, algorithm pinning, key handling for kid, jku and x5u, JWKS rotation, claim validation, the RFC 9068 access-token profile, RFC 8725 best practices, storage, revocation and the JWTs that non-human identities use. It is written for application security engineers, API and platform teams and identity architects who build or review JWT issuers and verifiers.
Key takeaways
- Configure, never negotiate: the verifier fixes the accepted algorithm, issuer, audience, type and key set in advance; nothing in the token header can widen that.
- Reject on any failed check: RFC 8725 requires the whole JWT to be rejected if any cryptographic operation fails, and ASVS 5.0 requires signature, validity time, type and audience checks before contents are used.
- Treat header key pointers as attacker input:
kid,jku,x5uandjwkmay only select from keys already trusted for that issuer. - Use explicit typing and separate keys per token class:
typvalues such asat+jwtand distinct audiences stop an ID token or client assertion being replayed as an access token. - Plan revocation before you need it: a JWT stays valid until
exp, so short lifetimes, sender-constrained tokens and a rehearsed signing-key rotation are the real kill switches.
How a JWT is built
A JWT (RFC 7519) is a set of claims inside a JOSE structure: a JSON Web Signature (JWS, RFC 7515) when signed or MACed, or a JSON Web Encryption object (JWE, RFC 7516) when encrypted. The compact JWS form is three base64url parts: protected header, payload and signature. A signed payload is encoded, not encrypted, so anyone holding the token can read it.
- Header parameters:
alg(algorithm),typ(media type),cty(JWTfor a nested token),kid(key ID hint) and the key-source parametersjku,x5u,jwkandx5c. RFC 7515 requires header parameters used in a trust decision to be integrity protected, and a recipient must reject a token whosecritlist names an extension it does not understand. - Unsecured JWTs: RFC 7519 defines tokens with
algset tononeand requires conforming libraries to implement HS256 andnone. Implementing is not accepting: RFC 7518 says unsecured objects must not be accepted by default. - Related formats: SD-JWT and verifiable credentials build selective disclosure on JWS. See the Verifiable Credentials Guide.
| Registered claim | Meaning | Verifier rule |
|---|---|---|
iss | Issuer | Exact match to the configured issuer; keys must belong to that issuer (RFC 8725 s3.8) |
sub | Subject (user, client or workload) | Meaningful only within the issuer's namespace |
aud | Intended recipients | Reject if the verifier is not listed; required when one issuer serves several relying parties (RFC 8725 s3.9) |
exp / nbf | Expiry and not-before times | Reject outside the window, allowing only small clock skew |
iat | Issued at | Optional sanity check on implausible values |
jti | Unique token ID | Replay detection and deny-list key |
Choosing and pinning the signing algorithm
RFC 8725 requires libraries to use only the algorithm the application expects and to allow only current algorithms. ASVS 5.0 V9.1.2 asks for a per-context allowlist without none, ideally either symmetric or asymmetric but not both; where both are needed, extra controls must prevent key confusion.
| Family | Examples | Key requirement | Choose when | Trade-off |
|---|---|---|---|---|
| HMAC | HS256, HS384, HS512 | At least the hash size (256 bits for HS256, RFC 7518), random, never a password (RFC 8725 s3.5) | Issuer and verifier are the same service or trust domain | Every verifier can mint tokens; weak secrets can be brute forced offline |
| RSA | RS256, PS256 | 2048 bits or larger | Many independent verifiers | Larger keys and tokens |
| Elliptic curve | ES256, EdDSA (RFC 8037) | Curve fixed by the algorithm | New deployments wanting small, fast signatures | Confirm every verifier library supports it |
| None | none | No key | Never for authentication or authorisation; RFC 9068 forbids it for access tokens | Anyone can forge tokens |
- Pin per issuer: store the expected algorithm with the issuer's key configuration and pass it explicitly to the verify call, as the OWASP Java JWT Cheat Sheet advises, rather than reading it from the token.
- Bind algorithm to key type: a public key must never be usable as an HMAC secret. RFC 7515 s10.7 recommends restricting algorithms and matching them to application data to prevent substitution.
- Asymmetric for distributed verification: when verifiers are separate from the issuer, public-key signatures mean a compromised API cannot mint tokens.
Key management: kid, jku, x5u and JWKS rotation
RFC 7517 defines a JWK Set as a keys array, usually published at a JWKS URI in OAuth or OpenID Connect metadata. The kid is only a case-sensitive hint. ASVS V9.1.3 requires verification keys to come from trusted, pre-configured sources for the issuer, with jku, x5u and jwk validated against an allowlist.
- kid: use it only to choose among keys already loaded for the configured issuer. Never pass it to a file path, SQL query or command; RFC 8725 s2.9 warns that values used for lookups become injection and SSRF vectors.
- jku and x5u: most verifiers should ignore them and use configured metadata. If used, require an exact allowlisted URL fetched over TLS with server identity validation (RFC 7515).
- jwk and x5c: an embedded key proves nothing unless it chains to a trust anchor configured for that issuer (OWASP JWT Cheat Sheet).
- One issuer, one key set: never verify against a union of keys from several issuers, and use separate keys or strict audience restriction per token class and audience (ASVS V9.2.4).
- Custody: keep signing keys in an HSM or KMS that signs in place, with an inventory, owner and retirement date for each. See the Cryptographic Key Management Guide.
JWKS rotation without outages
- Publish the new public key in the JWKS with a new
kidwhile still signing with the old key. - After the longest verifier cache period, switch signing to the new key.
- Remove the old public key once the longest token lifetime has passed.
- On an unknown
kidfrom a trusted issuer, verifiers refresh the JWKS once, rate limited, then reject. - For a suspected compromise, skip the overlap: remove the key at once and accept forced re-authentication.
Validating a JWT: the required checks
RFC 7519 s7.2 says failure at any validation step means rejection, and RFC 8725 s3.12 requires validation rules for different kinds of JWT to be mutually exclusive. Implement this validation checklist once, in a shared library or gateway.
| # | Check | Reject when | Source |
|---|---|---|---|
| 1 | Structure | Malformed encoding or JSON, or unsupported crit entries | RFC 7519 s7.2, RFC 7515 |
| 2 | Type | typ does not match the expected class (for example not at+jwt) | RFC 8725 s3.11, ASVS V9.2.2 |
| 3 | Algorithm | alg is not the pinned value for this issuer and key, or is none | RFC 8725 s3.1, ASVS V9.1.2 |
| 4 | Key source | Key not in the issuer's configured set; jku, x5u or jwk not allowlisted | ASVS V9.1.3 |
| 5 | Signature or MAC | Verification fails; no payload content is used before this passes | ASVS V9.1.1, V6.8.2 |
| 6 | Issuer | iss differs from the issuer that owns the key | RFC 8725 s3.8 |
| 7 | Audience | aud missing where required or not matching this service's allowlisted identifier | ASVS V9.2.3 |
| 8 | Time | Outside nbf to exp beyond a skew of a minute or two | RFC 7519, ASVS V9.2.1 |
| 9 | Replay and revocation | jti already used (for one-time tokens) or on the deny list | RFC 7519 s4.1.7 |
| 10 | Claim use | Claim values fail input validation before reaching queries, paths or URLs | RFC 8725 s3.10 |
To identify a user from an access token, ASVS V10.3.3 requires claims that cannot be reassigned, such as iss plus sub, not an email address.
JWT access tokens: the RFC 9068 profile
RFC 9068 (October 2021) standardises JWT access tokens so resource servers validate them consistently; its rules are a sound default for in-house APIs too. For flows and ID tokens, see the OAuth 2.0 and OpenID Connect Guide.
- Header:
typisat+jwtand resource servers reject other values;noneis forbidden and must be rejected. - Required claims:
iss,exp,aud,sub,client_id,iatandjti, plusscopewhen scopes were requested. - Validation: check
typ, decrypt if encrypted, matchissexactly, confirmaudcontains the resource server, verify the signature with the authorisation server's keys, checkexp, and returninvalid_tokenon failure. - Privacy: signed claims are readable by clients and anyone who captures the token, so clients must treat access tokens as opaque and personal data should stay out unless the token is encrypted.
- ID tokens are not access tokens: ASVS V9.2.2 accepts only access tokens for authorisation decisions.
Token attacks and the controls that stop them
This attack-class to control table draws on RFC 8725, the OWASP JWT Cheat Sheet and the OWASP API Security Top 10 2023, whose Broken Authentication entry lists accepting unsigned or weakly signed JWTs and skipping expiry checks. ATT&CK has no JWT-specific technique: forging falls under T1606 Forge Web Credentials, theft under T1528 Steal Application Access Token and replay under T1550.001 Application Access Token.
| Attack class | What goes wrong | Control |
|---|---|---|
| Unsecured token accepted | Verifier honours alg none | Pinned allowlist without none |
| Algorithm or key-type confusion | A public key is used as an HMAC secret because the header chose the routine | Algorithm bound to key type and issuer; no mixing MAC and public-key algorithms |
| Header key injection | Verifier trusts a key supplied or pointed to by jwk, jku, x5u or kid | Keys only from configured metadata; kid as lookup key only |
| Weak HMAC secret | Captured token is brute forced offline | 256-bit random secret in a vault, or asymmetric signing |
| Cross-JWT confusion and substitution | An ID token or a token for another API is accepted | Explicit typ, strict aud, separate keys per class |
| Expired or replayed token | No exp check, long lifetimes, bearer reuse | Short lifetimes, jti tracking, DPoP or mTLS binding |
| Claim injection | Header or claim values reach SQL, LDAP, paths or outbound URLs | Treat token content as untrusted input |
| Signing key compromise | Attacker mints valid tokens | HSM or KMS custody, rotation, issuance logging |
| JWE weaknesses | RSA1_5 oracle (RFC 7516 s11.4), unchecked inner signature, invalid ECDH-ES points, compression leaks (RFC 8725 s2.3 to s2.5, s3.6) | Sign then encrypt and validate both layers; one key per algorithm; single error for all decryption failures; no compression |
Lifetime, revocation and replay
A self-contained JWT stays valid until it expires. NIST IR 8587 (September 2026) recommends that access and identity tokens be valid for no more than one hour, and shorter for higher-risk resources. Most deployments combine at least two of these patterns.
| Pattern | How it works | Cost |
|---|---|---|
| Short lifetime plus refresh | Access tokens of minutes; refresh tokens rotated with an absolute expiry (ASVS V10.4.8) | More token traffic |
| Deny list | Revoked iss plus jti held until exp and checked per request | Shared state; loses some stateless benefit |
| Reference tokens | Opaque token checked by introspection (RFC 7662) | Call or cache per request; immediate revocation |
| Sender-constrained tokens | Bound to a client key with DPoP (RFC 9449) or mTLS (RFC 8705) | Client and server changes; stolen tokens fail (ASVS V10.3.5) |
| Signing-key removal | Every token signed by the key fails | Mass re-authentication; last-resort kill switch |
Key the deny list on iss and jti, not the raw token or its hash: the OWASP JWT Cheat Sheet warns that token encodings are malleable. Continuous access evaluation and session revocation are covered in the Token and Session Security Guide.
Storage and transport
- Transport: send JWTs over TLS in the
Authorization: Bearerheader (RFC 6750), never in URLs, where they reach logs, history and referrer headers. - Browser storage: ASVS V10.1.1 prefers a backend-for-frontend so tokens never reach browser JavaScript. The OWASP Java JWT Cheat Sheet instead uses
sessionStoragewith a strict Content Security Policy. A BFF removes the XSS exposure;sessionStoragelimits persistence, not script access. - Sidejacking defence: OWASP's fingerprint pattern puts a random value in a hardened
HttpOnlycookie and only its SHA-256 hash in the token. Do not bind to IP addresses, which change legitimately. - Telemetry: strip
Authorizationheaders from application, proxy and APM logs.
JWTs for workloads and non-human identities
- Client assertions (RFC 7523): a client authenticates to the token endpoint with a signed JWT (
client_assertion_typeurn:ietf:params:oauth:client-assertion-type:jwt-bearer, calledprivate_key_jwtin OpenID Connect).issandsubmust be theclient_id,audmust identify the authorisation server andexpis required. RFC 7523 does not mandate replay protection, so keep expiry short and trackjti. The private key never leaves the client, unlike a shared client secret. - Workload identity federation: CI platforms and Kubernetes issue short-lived OIDC JWTs that clouds exchange for access. The trust policy is JWT validation: pin the issuer, require the exact audience and match
subprecisely (repository, branch, namespace, service account) instead of broad wildcards. See the Workload Identity Federation Guide. - Service-to-service calls: give each downstream API its own audience rather than forwarding the caller's token everywhere. See the OAuth Token Exchange and Delegation Guide.
- Signing secrets are NHI credentials: HMAC secrets and private keys need an owner, vault storage, rotation and offboarding. See the NHI Authentication Guide.
How to implement
- Inventory: list every issuer, key, algorithm, verifier and token class. Flag HMAC secrets shared across services and keys outside a KMS.
- Centralise validation: one maintained library or gateway policy, configured per issuer with algorithm, key source, audience, type and skew.
- Audit mode: run the stricter rules in log-only mode for two to four weeks and fix issuers that omit
aud,typorjti. - Pilot ring: enforce on internal APIs, then external ones, watching rejection rates by reason.
- Enforce and shorten: reject everywhere, then cut lifetimes and add refresh rotation and deny-list checks.
- Rehearse rotation: run a planned JWKS rotation and an emergency key removal, timing how long verifiers take to converge.
- Roll back safely: keep enforcement behind a per-service flag and the previous public key in the JWKS during rollout.
Small organisations should consume their identity provider's tokens through a framework or gateway validator rather than issuing their own JWTs. Large estates benefit from a platform-owned validation library, audiences managed as policy as code and sender-constrained tokens for high-value APIs.
Detection and measurement
- Rejections by reason: bad signature, disallowed
alg, unknownkid, wrongaudortyp, expired. A spike in one reason means probing or a broken issuer. - Issuance records: log
jti,sub,client_idand key ID at issue, so valid-looking tokens no issuer created stand out as a sign of key compromise. - Key use: alert on KMS signing calls from unexpected principals and on JWKS changes outside change windows.
Metrics: percentage of verifiers on the central library with pinned settings; maximum access-token lifetime per issuer (target one hour or less); percentage of signing keys in an HSM or KMS with an owner; time for all verifiers to drop a removed key in the last drill; number of HMAC secrets shared by more than one service (target zero); tokens presented with no issuance record (target zero).
Common failure patterns
- Trusting the header: reading
alg,jkuorjwkfrom the token to decide how to verify it. - Signature-only validation: no
audortypcheck, so a token for one API, or an ID token, opens another. - Passwords as HMAC secrets: short secrets copied into every service, where one leak lets anyone mint tokens.
- Keys that never retire: long-lived signing keys on developer machines, not rotated at offboarding. The Coupang breach involved an authentication token signing key that was not revoked after an engineer left.
- Day-long tokens without revocation: logout and account disablement change nothing until
exp.
Lessons from incidents
- Issuer scope is part of validation: Microsoft's July 2023 analysis said Storm-0558 used an acquired Microsoft account (MSA) consumer signing key to forge tokens, and a validation issue allowed that key to be trusted for Azure AD tokens. Microsoft revoked all MSA signing keys valid at the time and issued new ones in more isolated systems. This is RFC 8725 s3.8 and ASVS V9.2.4 in practice. See the Microsoft Storm-0558 key breach.
- Spoofed JWTs as an authentication bypass: CISA's Known Exploited Vulnerabilities catalog lists CVE-2023-29357 in Microsoft SharePoint Server, where an unauthenticated attacker with spoofed JWT authentication tokens could gain administrator privileges; CISA records known ransomware use. Patch verifier software as urgently as issuers.
Framework mapping
| Control area | OWASP ASVS 5.0 | NIST SP 800-53 Rev. 5 | Other |
|---|---|---|---|
| Signature and algorithm allowlist | V9.1.1, V9.1.2, V6.8.2 | SC-13, SC-23 | CSF 2.0 PR.AA-03; ISO/IEC 27001:2022 A.8.24 |
| Trusted key sources and custody | V9.1.3 | SC-12 | ATT&CK T1606 (threat) |
| Time, type and audience claims | V9.2.1 to V9.2.4, V10.3.3 | IA-5, SC-23 | ISO/IEC 27001:2022 A.8.5 |
| Lifetime and revocation | V7.4.1, V10.4.8 | AC-12 | D3FEND D3-TBA Token-based Authentication |
| Stolen-token resistance | V10.3.5, V10.1.1 | SC-23 | D3FEND D3-TB Token Binding; ATT&CK T1528, T1550.001 (threats) |
| Validation and issuance logging | n/a | AU-2 | CSF 2.0 DE.CM-03 |
RFC 8725 (BCP 225, February 2020) is the current best practice. Its successor, draft-ietf-oauth-rfc8725bis, was approved and in the RFC Editor queue in October 2026 but remains an Internet-Draft until published. Its changes include case-insensitive alg handling checks, PBES2 iteration and decompression limits, deprecating RSA-PKCS1 v1.5 encryption and treating kid, jku and x5u as attacker-controllable.
Practitioner checklist
- Pin algorithm, issuer, key set, audience and
typper token class in one shared validator. - Disable
noneand reject anyalgthat does not match the pinned value for its key. - Ignore
jku,x5uandjwkunless allowlisted; usekidonly to select a pre-loaded key. - Prefer asymmetric signing; where HMAC remains, use 256-bit random secrets from a vault, one per trust domain.
- Keep signing keys in an HSM or KMS with owners, rotation dates and a rehearsed emergency removal.
- Adopt RFC 9068 for access tokens and never accept ID tokens for authorisation.
- Keep access tokens to an hour or less, rotate refresh tokens with absolute expiry and keep an
issplusjtideny list. - Bind high-value tokens to the client with DPoP or mTLS.
- Validate client assertions and federation tokens with exact
audandsubmatching. - Log rejections by reason and issuance by
jti, and alert on tokens with no issuance record.
Standards and references
- IETF: RFC 8725 JSON Web Token Best Current Practices
- IETF: RFC 7519 JSON Web Token (JWT)
- IETF: RFC 7515 JSON Web Signature (JWS)
- IETF: RFC 7518 JSON Web Algorithms (JWA)
- IETF: RFC 9068 JWT Profile for OAuth 2.0 Access Tokens
- IETF: RFC 7523 JWT Profile for OAuth 2.0 Client Authentication and Authorization Grants
- OWASP: Application Security Verification Standard 5.0
- OWASP: JSON Web Token Cheat Sheet
Related NHI Mgmt Group resources: Token and Session Security Guide · OAuth 2.0 and OpenID Connect Guide · Verifiable Credentials Guide · Workload Identity Federation Guide · API Security Guide