All resources
Application Security9 min read

JWT anatomy and validation without accidental trust

Understand what a JSON Web Token proves, what it does not prove, and how to validate it safely at an API boundary.

A practical PingFlow guide for developers working at the boundary between systems.

At a glance

Key takeaways

  • A token is a claim container, not a session database
  • Validate every claim that affects a decision
  • Pin algorithms and choose keys deliberately
In this guide

A token is a claim container, not a session database

A JSON Web Token has an encoded header, payload, and signature. The header describes the algorithm and often a key identifier. The payload contains claims such as subject, issuer, audience, expiration, and issued-at time. The signature lets a verifier detect changes made after signing. None of those parts are secret. Base64url encoding is a transport format, not encryption, so anyone holding a token can decode its claims. Do not place passwords, API keys, payment details, or private customer notes in a JWT.

The token proves only what the issuer signed under the agreed verification rules. It does not prove that the current user still has an active subscription, that the account has not been disabled, or that a downstream permission has not changed. Keep short-lived authentication claims separate from authoritative authorization data. A service can use a token to identify a principal, then check current policy before performing a sensitive action.

Validate every claim that affects a decision

A secure verifier checks the signature and the claims that define its trust boundary. Verify the issuer against an exact allowlist, verify the audience for the current API, require an expected subject format, and enforce expiration. If the token includes not-before, reject it before that time. Use a small clock-skew allowance for distributed systems, but do not make the allowance so large that an expired token remains useful for hours. Treat a missing required claim as invalid rather than filling in a default.

Do not accept a token because a library returned a decoded payload. Most libraries separate decoding from verification, and a careless caller can use the first result. Wrap the library with one application function that verifies algorithm, key, claims, and error handling in a fixed order. Make the safe function the only import used by request handlers. This prevents one endpoint from silently using weaker defaults than another.

Pin algorithms and choose keys deliberately

The alg header is input from the caller. It should not decide which algorithms your server accepts. Configure an explicit set, normally one modern asymmetric algorithm or a tightly controlled family, and reject everything else. Never treat a token as valid by switching from an asymmetric algorithm to a symmetric one and using a public key as an HMAC secret. That class of algorithm confusion exists when verification follows untrusted header values.

For rotating asymmetric keys, retrieve a trusted key set by issuer and select a key by the header key ID. Cache the set with a bounded lifetime and a fallback for short network failures, but do not accept an unknown key indefinitely. If a key disappears, fail closed for tokens that require it and alert the operator. Keep key retrieval separate from user input; a token must not be able to choose an arbitrary URL for key discovery.

Choose token lifetime and revocation behavior

Short-lived access tokens reduce the impact of a stolen token, but they require a refresh strategy. Use a refresh token or a server-side session to obtain new access tokens, and store refresh credentials with stronger protection. Avoid putting long-lived bearer tokens in browser local storage when an HttpOnly, secure cookie can reduce exposure to script access. Consider the threat model of the client before selecting a storage mechanism.

Revocation is not automatic for stateless tokens. If a user changes a password, loses a device, or is suspended, an already issued token may remain valid until expiration. For high-risk actions, check a session version or a current account status in the database. A denylist can revoke individual token identifiers but creates storage and cleanup work. State the tradeoff in the design rather than promising instant revocation that the system cannot provide.

Handle tokens at the HTTP boundary

Read bearer tokens from the Authorization header according to a strict grammar. Reject duplicate authorization headers, unexpected schemes, excessive token length, and values containing control characters. Do not accept a token in both a header and a query parameter because ambiguous precedence creates debugging and logging hazards. Remove authorization headers before proxy logs or error reporting systems receive them.

Return a consistent authentication failure without revealing whether the token was expired, signed by an unknown key, or issued for another audience. Internal logs can record a request ID, issuer category, key ID, and failure class without storing the token. Rate-limit repeated failures at the edge when appropriate. A token verifier is part of an internet-facing parser, so resource limits matter alongside cryptography.

Test the negative cases first

Build fixtures for an expired token, a token issued in the future, a wrong issuer, a wrong audience, a missing subject, a changed payload, a changed header, an unknown key ID, an unsupported algorithm, and a token with an invalid signature. Test clock boundaries with a fake time source. Include a token that is structurally valid but contains claims of the wrong type, such as an array where a string is expected. These cases catch permissive decoders and library defaults.

Exercise the complete middleware stack because another component may decode or replace the token before your verifier runs. Verify that a rejected token cannot reach authorization logic and that a valid token still results in a current permission check. Add a regression test whenever a provider rotates keys or changes claim formats. Security behavior should be boring, repeatable, and visible in code review.

Document the trust relationship

Write down which issuer signs tokens, which audiences each service accepts, how keys are discovered and cached, the clock-skew allowance, token lifetime, refresh behavior, and the steps for emergency key rotation. Include an example with placeholder values but never commit a real token. Define which claims are display hints and which claims are allowed to authorize an action. This prevents a UI convenience claim from becoming an access-control shortcut.

A well-designed JWT boundary is small. It verifies a precisely defined token, translates it into a principal, and hands authorization to current policy. When a token-related incident occurs, preserve the header and claim names in a redacted diagnostic record, not the bearer credential itself. Evidence helps you fix the verifier without creating another credential leak.

Implementation example

Treat a JWT as a signed statement whose meaning comes from a trusted issuer and an explicit contract. Validate the algorithm against an allowlist, resolve the issuer's key by `kid`, check issuer, audience, subject, expiry, not-before, and required scopes, then authorize the requested resource. Decoding a payload is useful for debugging but proves nothing by itself.

text
verify(token, key, {
  algorithms: ['RS256'],
  issuer: 'https://issuer.example',
  audience: 'pingflow-api'
})

Verify and troubleshoot

Test a valid token, expired token, wrong audience, wrong issuer, unknown key ID, disallowed algorithm, missing scope, and token with a future not-before time. Verify that the API rejects a token signed by an old key after the rotation window and that error responses do not reveal key or claim details. Exercise clock skew at the documented tolerance.

Operations and recovery

Cache public keys for a bounded time and refresh on an unknown key ID without turning a token into an outbound-request denial-of-service vector. Rotate signing keys with overlap, publish the new key before issuing tokens, and keep an emergency revoke or audience-change path. Never place secrets or sensitive personal data in claims just because the token is encoded.

References and further reading

Use RFC 7519 for JWT, RFC 8725 for JWT best current practices, and the issuer's JWKS rotation documentation. Define authorization separately from authentication so a valid token cannot become blanket access.

Keep exploring