WritingJWT Security Best Practices: Signing, Storage, and Revocation — Clixo
6 min readjwt, security, authentication, best-practices, tokens

JWT Security Best Practices: Signing, Storage, and Revocation

A practical best-practices guide to JWT security — covering algorithm choice, safe storage, token revocation patterns, and the pitfalls that get teams burned.

JWTs are everywhere — and so are JWT security mistakes. The format is simple enough to misuse, and many tutorials skip the security-critical details in favor of getting something working quickly. The result is production systems with tokens that cannot be revoked, secrets that are too short, or algorithms that have been swapped by an attacker.

This is the list of JWT security best practices your implementation needs to follow.

JWT Security Best Practices: Start With the Algorithm

The most dangerous JWT vulnerability is algorithm confusion. Early JWT libraries allowed the alg field in the token header to dictate which algorithm the server used for verification. An attacker could change RS256 to none and receive a token that was accepted without any verification.

Enforce the algorithm on the server. Never accept the algorithm from the token header. Hardcode it in your verification code.

Use RS256 or ES256 for anything that leaves your servers. Asymmetric algorithms let you share a public key for verification without exposing the signing key. Services that only need to verify tokens never need access to the private key.

Use HS256 only for internal, single-service tokens where one service signs and the same service verifies. A shared HMAC secret that escapes one service compromises everything signed with it.

Never use none. Configure your library to reject it explicitly if the library does not already.

Keep Tokens Short-Lived

The inability to revoke a JWT is its defining limitation. The shorter the token lives, the smaller the window an attacker has if a token is compromised.

  • Access tokens: 15 minutes is a reasonable default. Go shorter for high-security actions.
  • Refresh tokens: 7-30 days, stored server-side so they can be revoked.
  • ID tokens (OIDC): used only for client-side identity display, not for API access. Keep them short.

A 24-hour access token is effectively a session you cannot end. Do not treat long expiry as a convenience feature.

Store Tokens Safely

Where you store a JWT determines the attack surface.

For web applications:

  • Store tokens in HttpOnly, Secure, SameSite=Lax cookies. JavaScript cannot read them, eliminating XSS-based theft.
  • Do not store tokens in localStorage or sessionStorage. Any XSS vulnerability on your page — including injected third-party scripts — can exfiltrate them.

For mobile applications:

  • iOS: Keychain Services
  • Android: EncryptedSharedPreferences or Android Keystore
  • Avoid storing tokens in plain shared preferences or SQLite without encryption.

For server-to-server:

  • Pass tokens in the Authorization: Bearer header.
  • Do not log request headers in production without scrubbing the Authorization value.

Sign With a Sufficient Secret

If you are using HS256, the secret must be long enough to resist brute force. A common mistake is using a human-readable password as the signing secret.

  • Use at least 256 bits (32 bytes) of cryptographically random data.
  • Generate it with a secure random number generator, not a password or UUID.
  • Rotate secrets periodically. Run overlapping signing secrets during rotation so existing tokens do not immediately break.

For RS256 or ES256, use a minimum 2048-bit RSA key or 256-bit EC key. Store private keys in a secrets manager, not in environment variables committed to source control.

Validate All Claims

Signature verification alone is not sufficient. After verifying the signature, validate:

  • exp — the token must not be expired.
  • nbf — the token must not be before its valid-from time, if set.
  • iss — the issuer must match your expected value.
  • aud — the audience must match your service identifier. A token minted for Service A should not be accepted by Service B.

Libraries handle these automatically if you configure them. Make sure you are passing expected values to the verifier, not relying on defaults that may be permissive.

Implement Refresh Token Rotation

Refresh tokens grant long-lived access and are therefore higher-value targets. Rotate them on every use.

  1. User presents a refresh token.
  2. Server verifies it, issues a new access token and a new refresh token.
  3. Server invalidates the old refresh token.
  4. If the old refresh token is ever presented again, treat it as a signal of token theft. Invalidate all refresh tokens for that user.

This pattern — sometimes called refresh token rotation with reuse detection — limits the blast radius of a stolen refresh token to a single use.

Build a Revocation Path

The most common gap in JWT implementations is no revocation path at all. Before you ship, answer: "If a user's token is stolen, how do I stop them from using it?"

Practical options:

  • Short expiry. The simplest form of revocation. If tokens expire in 15 minutes, the damage window is 15 minutes.
  • Refresh token invalidation. Invalidate the refresh token to prevent new access tokens from being issued. The current access token lives out its short expiry.
  • Token blocklist in Redis. Store the jti claim of revoked tokens with a TTL matching the token expiry. Check on each request. This adds a lookup, but for high-security applications it is the right call.
  • Session invalidation signal. Issue a short-lived version field with the user record. If the token's version does not match the current value, reject it. This requires one lightweight database read per request.

Include Only the Claims You Need

JWTs are often used to carry user data from the auth service to application services. Be selective about what you include.

  • Do not include sensitive PII that downstream services do not need.
  • Do not include mutable data that may become stale. If a user's role changes, a JWT with the old role will remain valid until expiry.
  • Include only the claims required to make authorization decisions without an additional database lookup.

JWT implementation is a set of decisions, not just a library call. Each of the choices above has a security consequence in one direction or another. Getting them right at the start is straightforward; auditing and fixing them in a production system with tokens already in the wild is considerably more work.

If you need a JWT-based auth system built correctly from the start, Clixo designs and ships production authentication systems for teams who want it done right without the rework.