Skip to content

JWT Security: Best Practices, Common Mistakes, and Safe Usage

JSON Web Tokens (JWT) are everywhere in modern authentication — and so are the security mistakes that come with them. This guide covers how JWT works, the most common vulnerabilities, and the best practices every developer should follow.

By Guangming ChenUpdated 2026-08-01

What is JWT?

JWT (JSON Web Token) defined in RFC 7519 is a compact, URL-safe means of representing claims to be transferred between two parties. A JWT encodes a JSON object as three Base64URL-separated parts: a header, a payload, and a signature. The signature allows the receiver to verify that the token has not been tampered with, making JWT a popular choice for stateless authentication in APIs, single-page applications, and microservices.

Unlike opaque session identifiers, a JWT is self-describing: the payload itself carries claims such as the issuer (iss), subject (sub), expiration time (exp), and custom user data. This self-contained design enables stateless verification — the server does not need to look up the token in a database to validate it, only the signing key.

JWT Structure: Header.Payload.Signature

A JWT is composed of three Base64URL-encoded parts separated by dots (xxxxx.yyyyy.zzzzz):

  • Header: JSON describing the token type (typ: JWT) and the signing algorithm (e.g., HS256, RS256).
  • Payload: JSON containing the claims — registered claims (iss, sub, aud, exp, nbf, iat, jti), public claims, and private claims defined by your application.
  • Signature: The cryptographic signature computed over the encoded header and payload using the algorithm declared in the header and a secret or private key.

Important: the header and payload are only encoded, not encrypted. Anyone who intercepts a JWT can decode and read its contents. Confidentiality must never rely on the token encoding — sensitive data such as passwords, credit card numbers, or personal secrets must never be placed in a JWT payload.

Security Risks: Algorithm Confusion and Weak Secrets

The algorithm confusion attack exploits JWT libraries that trust the alg field in the token header. If a server signs tokens with RS256 (asymmetric, public/private key pair) but the verifier accepts alg: none or HS256, an attacker can forge tokens by re-signing them with the server’s public key treated as an HMAC secret. The defense is straightforward: pin the expected algorithm on the server side and reject any token whose header claims a different algorithm.

Weak secrets remain a persistent problem. Many teams use short or guessable HMAC secrets, which can be cracked offline using tools that brute-force common passwords and dictionary words against captured tokens. An HMAC secret should be a long, random, high-entropy value generated by a cryptographic random generator — never a project name, domain, or human-readable phrase.

Security Risks: Token Leakage and Expiration

Token leakage remains one of the most common causes of JWT-related breaches. Tokens stored in localStorage are accessible to any JavaScript running on the page, including malicious scripts injected through cross-site scripting (XSS). Tokens placed in URLs can leak into server logs, browser history, and Referer headers. Once a JWT is stolen, the attacker can impersonate the victim until the token expires — and because JWT is stateless, the server has no easy way to revoke it.

Missing or weak expiration is another frequent flaw. A JWT without an exp claim, or one with an excessively long lifetime, hands an attacker a permanent credential if the token is leaked. Every JWT should include an exp claim with a short, appropriate lifetime, and long-lived access should be handled through refresh tokens that can be rotated and revoked.

Best Practices for Safe JWT Usage

Short expiry times combined with refresh tokens offer the best balance between security and usability. Access tokens should expire in minutes rather than hours or days, limiting the damage of a stolen token. Refresh tokens, stored securely (preferably in HttpOnly, Secure, SameSite cookies), allow users to obtain new access tokens without re-authenticating, and can be rotated and revoked server-side when compromise is suspected.

Additional best practices recommended by RFC 8725:

  • Always use HTTPS so tokens cannot be intercepted in transit.
  • Store tokens in HttpOnly cookies when possible; avoid localStorage for sensitive tokens.
  • Pin the signing algorithm server-side; never trust the alg header from the token.
  • Use strong, high-entropy secrets for HMAC; prefer asymmetric (RS256/ES256) keys when possible.
  • Validate all registered claims (iss, aud, exp, nbf) on every request.
  • Implement a token revocation strategy for logout and compromised tokens.

Comparison Table: JWT vs Session vs OAuth

AspectJWTSessionOAuth
State modelStateless (self-contained)Stateful (server-side store)Framework / protocol
StorageClient-side (cookie or storage)Server-side session storeVaries (token-based)
ScalabilityHigh (no shared session store)Lower (needs shared store)High
ExpirySelf-contained exp claimServer-controlledToken-based, refreshable
RevocationDifficult (stateless)Easy (delete session)Via refresh token rotation
Best forAPI auth, microservicesWeb apps, simple authThird-party delegated access
Main riskToken leakage, alg confusionSession hijackingToken theft, redirect attacks

Technical Reference: RFC 7519

RFC 7519 defines the JWT structure, the standard registered claims, and the serialization rules. The signing algorithms used by JWT are defined in RFC 7518 (JSON Web Algorithms, JWA), which covers HMAC (HS256/HS384/HS512), RSA (RS256 and friends), ECDSA (ES256 and friends), and the none algorithm that should always be disabled.

For real-world deployment, RFC 8725: JSON Web Token Best Current Practices is essential reading. It codifies the lessons learned from years of JWT misuse, including algorithm pinning, key management, claim validation, and when not to use JWT at all.

  • RFC 7519: Defines the JWT compact serialization and registered claims (iss, sub, aud, exp, nbf, iat, jti).
  • RFC 7518 (JWA): Defines the cryptographic algorithms for signing and (separately) encrypting JWTs.
  • RFC 8725: Best current practices — algorithm pinning, key strength, validation, and explicit guidance against alg: none.

Frequently Asked Questions

Is JWT encrypted by default?

No. By default, a JWT is only Base64URL-encoded and cryptographically signed — it is not encrypted. Anyone who obtains the token can decode and read the header and payload. If confidentiality is required, you must use JWE (JSON Web Encryption) or encrypt the payload before encoding.

What is the algorithm confusion attack in JWT?

The algorithm confusion attack exploits JWT libraries that trust the alg field in the token header. If a server uses RS256 (asymmetric) but the verifier accepts alg: none or HS256, an attacker can re-sign the token using the server’s public key as an HMAC secret. The fix is to pin the expected algorithm server-side and reject any token whose header claims a different algorithm.

Where should I store JWT tokens in the browser?

Prefer HttpOnly, Secure, SameSite cookies for storing tokens, because they are not accessible to JavaScript and are protected against XSS-based theft. Avoid storing sensitive tokens in localStorage or sessionStorage, since any JavaScript running on the page — including injected malicious scripts — can read them.

How long should a JWT access token live?

Access tokens should be short-lived — typically 5 to 15 minutes. This limits the damage if a token is leaked. For longer sessions, use a refresh token with rotation and server-side revocation, so users can obtain new access tokens without re-authenticating while keeping the access-token lifetime short.

Related Tools