Skip to content
Ashish.
All posts
Diagram illustrating JWT validation layers: cryptographic handshake, identity graph, and operational context.
6 min readBackendBackend Engineers, Security EngineersFeatured#jwt#security#backend#authentication#tokens#validation#checklist#production

A JWT Validation Checklist for Production

A practical checklist for backend and security engineers to ensure reliable JWT validation, token verification, and claims validation in production environments.

By Ashish KumarPart 8 of JWT From the Spec Up

JSON Web Tokens (JWTs) are often treated as a drop-in authentication primitive. Developers copy-paste libraries, call a verify() function, and assume security. This assumption is fatal. JWT validation is not a single atomic operation; it is a multi-stage process where failure at any layer—cryptographic, logical, or transport—results in total authentication bypass.

For backend and security engineers, robustness comes from understanding the mechanism of trust. You are not just parsing JSON; you are validating a signed assertion against a specific set of constraints. This checklist breaks down the validation pipeline into three critical layers: cryptographic verification, claims validation, and operational context.

Part 8 of the JWT From the Spec Up series.

1. The Cryptographic Handshake: Algorithm and Key

The first step in JWT validation is determining how the token was signed and who signed it. This happens in the Header and the signature verification phase.

Enforce an Explicit Algorithm Allowlist

The most critical vulnerability in JWT implementations is the acceptance of unexpected algorithms. The alg header in the JWT determines the verification method. If your library defaults to HS256 (HMAC with SHA-256) and the attacker provides a token with alg: "none", many libraries will skip signature verification entirely.

Mechanism: The verifier must read the alg header and strictly match it against a predefined list of acceptable algorithms.

// DANGEROUS: Implicitly accepts any algorithm
jwt.verify(token, secret);
 
// SECURE: Explicit allowlist
const ALLOWED_ALGORITHMS = ['RS256', 'ES256']; // Prefer asymmetric keys
jwt.verify(token, publicKey, { algorithms: ALLOWED_ALGORITHMS });

Opinion: Avoid HMAC (HS256) in multi-tenant or microservice architectures. If you use HMAC, the same secret key signs and verifies tokens. If any service leaks the secret, the entire system is compromised. Use asymmetric algorithms (RS256, ES256) where the public key verifies and the private key signs.

Validate the Key Source

When using asymmetric algorithms, the public key must be trusted. Never hardcode public keys if they rotate.

Mechanism: Fetch the public key from a trusted metadata endpoint (e.g., JWKS - JSON Web Key Set) provided by the Identity Provider (IdP). Cache these keys securely to avoid latency on every request.

2. The Identity Graph: Claims Validation

Once the signature is valid, you have a JSON object. You must now validate the claims (the payload). A valid signature does not mean the token is valid for your context. Proper JWT validation requires strict adherence to claim constraints.

Verify the Issuer (iss)

The iss claim identifies the principal that issued the JWT.

Mechanism: Compare the iss value in the token against your expected issuer URI (e.g., https://auth.yourcompany.com). This prevents accepting tokens from a compromised or spoofed identity provider.

Verify the Audience (aud)

The aud claim identifies the recipients for whom the JWT is intended.

Mechanism: If your API is api.yourcompany.com, ensure the aud claim contains this value. This prevents token reuse attacks where a token issued for one service (e.g., a mobile app) is used to access another (e.g., an admin panel).

Enforce Expiration (exp) and Clock Skew

The exp claim specifies the expiration time. You must check this at the moment of validation.

Mechanism: Compare the exp timestamp against the current server time. Crucially, account for clock skew between your server and the IdP.

// Account for up to 60 seconds of clock skew
const now = Math.floor(Date.now() / 1000);
if (payload.exp < now - 60) {
  throw new Error('Token expired');
}

Note: Never rely on client-side time. Always use server-side time for validation.

Optional: Validate sub and Custom Claims

The sub (subject) claim typically identifies the user. Validate its format (e.g., UUID, numeric ID) to prevent injection attacks if you use it in database queries. For custom claims (e.g., roles, permissions), define strict schemas. Do not trust arbitrary data.

3. The Operational Context: Transport and Revocation

JWT validation doesn't end with the signature and claims. How the token is transmitted and managed affects its security.

Token Storage and Transmission

JWTs are bearer tokens. Whoever holds the token can impersonate the user.

Mechanism:

  • HttpOnly Cookies: Recommended for web apps. Prevents JavaScript access (mitigates XSS theft).
  • Authorization Header: Standard for APIs. Requires protection against CSRF.
  • LocalStorage: Avoid. Accessible by any JavaScript on the page, making tokens vulnerable to XSS.

Implement Revocation with jti

JWTs are stateless, which makes revocation difficult. If a user logs out, the token remains valid until it expires.

Mechanism: Use the jti (JWT ID) claim to uniquely identify each token. Store revoked jti values in a fast store (e.g., Redis) with a TTL matching the token's expiration. On validation, check if the jti is in the revocation list.

// Using node-redis client
const isRevoked = await redisClient.sIsMember('revoked_jtis', payload.jti);
if (isRevoked) {
  throw new Error('Token revoked');
}

4. Production Validation Checklist

Use this checklist to audit your implementation. Each item corresponds to a specific mechanism failure point in JWT validation.

Cryptographic Checks

  • Algorithm Allowlist: Is alg explicitly validated against a whitelist? Is none excluded?
  • Key Trust: Are public keys fetched from a trusted JWKS endpoint? Are they cached?
  • Signature Verification: Is the signature verified before any claims processing?

Claims Checks

  • Issuer (iss): Is the issuer verified against a known, trusted URI?
  • Audience (aud): Is the audience verified to prevent cross-service token reuse?
  • Expiration (exp): Is exp checked against server time? Is clock skew accounted for?
  • Not Before (nbf): Is nbf checked if the token is intended for future use?
  • Subject (sub): Is the subject validated for format and existence in the user database?

Operational Checks

  • Revocation: Is jti used for token revocation? Is there a backend store for revoked tokens?
  • Transmission: Are tokens stored in HttpOnly cookies or sent via Authorization headers? Is localStorage avoided?
  • Secret Rotation: Is there a process for rotating signing keys without invalidating existing tokens? (Use kid header to identify keys.)
  • Logging: Are validation failures logged for security monitoring? (Do not log token contents.)

Conclusion

JWT validation is a defense-in-depth strategy. No single check is sufficient. You must enforce cryptographic integrity, logical constraints, and operational controls. By treating JWTs as signed assertions rather than magic strings, you build systems that resist common attacks like algorithm confusion, token replay, and unauthorized access.

Implement this checklist rigorously. In production, security is not a feature; it is a constraint.

Related posts