JSON Web Tokens (JWT): Security Best Practices
JSON Web Tokens, or JWTs, are a compact, self-contained format for transmitting signed claims between parties. They are widely used for authentication and authorization because a server can verify a token's integrity without a database lookup, enabling stateless sessions and clean service-to-service communication. That convenience comes with sharp edges, and several well-known JWT vulnerabilities stem from validating tokens incorrectly.
Anatomy of a JWT
A JWT consists of three base64url-encoded parts separated by dots: a header, a payload, and a signature.
eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9 header: {"alg":"RS256","typ":"JWT"}
eyJzdWIiOiIxMjMiLCJleHAiOjE3MH0 payload: {"sub":"123","exp":1700000000}
SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV signature over header.payload
The header names the signing algorithm. The payload carries claims, such as the subject, expiration time, issuer, and audience. The signature is computed over the header and payload using either a shared secret (HMAC algorithms such as HS256) or a private key (asymmetric algorithms such as RS256). The three parts are joined with dots into a single compact string that travels comfortably in an HTTP header, which is part of why the format is popular for APIs. Anyone can read the payload, since it is only encoded, not encrypted, so a JWT must never carry secrets in plaintext.
Common JWT Vulnerabilities
The alg None Attack
Some libraries have honored an alg value of none, meaning the token is unsigned. If a server accepts such a token, an attacker can forge arbitrary claims. Always reject tokens whose algorithm is not one you explicitly expect.
Algorithm Confusion
In this attack, a server that verifies RS256 tokens is tricked into treating the token as HS256. The attacker signs a forged token using the server's public key as the HMAC secret. If the library uses one verification function that picks the algorithm from the token header, the forgery may pass. Pin the expected algorithm rather than trusting the header.
Weak Signing Secrets
HMAC-signed tokens are only as strong as their secret. A short or guessable secret can be brute-forced offline, letting an attacker mint valid tokens. Use long, random secrets, or prefer asymmetric keys.
Missing Claim Validation
A structurally valid signature is not enough. Failing to check the expiration (exp), issuer (iss), and audience (aud) claims lets attackers replay expired tokens or use a token intended for a different service.
JWT Security Best Practices
Validate Rigorously
- Pin the algorithm. Configure verification to accept only the specific algorithm your system uses, and never derive it from the token.
- Verify the signature against the correct key before trusting any claim.
- Check standard claims, including expiration, not-before, issuer, and audience, on every request.
- Reject unexpected tokens, such as unsigned tokens or those referencing an unknown key.
Keep Lifetimes Short and Plan for Revocation
Because a signed JWT is valid until it expires, it is hard to revoke. Use short-lived access tokens paired with longer-lived refresh tokens that can be invalidated server-side. Maintain a way to reject compromised tokens, such as a denylist or a rotating signing key.
Store Tokens Carefully
Where a token lives in a browser affects its risk. Storing tokens in localStorage exposes them to theft through cross-site scripting. Storing them in cookies with HttpOnly, Secure, and SameSite attributes, described in secure cookies, removes them from JavaScript's reach but requires CSRF protection. Choose based on your threat model.
Protect the Keys
Guard signing keys as critical secrets, rotate them periodically, and use a key identifier (kid) so you can roll keys without downtime. For asymmetric algorithms, distribute only the public key to verifiers.
Prefer Vetted Libraries and Weigh Alternatives
Implementing token verification by hand is a common source of the flaws above. Use a well-maintained library that validates the algorithm, signature, and claims by default, and configure the expected values explicitly rather than relying on assumptions. Where a use case does not truly need self-contained tokens, a traditional server-side session with an opaque identifier avoids much of this complexity, because revocation and validation happen naturally on the server.
JWTs in Context
JWTs are frequently issued by identity systems built on OAuth 2.0 and OpenID Connect, where the ID token is a JWT describing the authenticated user. Understanding how a token is issued, scoped, and validated across those flows is essential to using JWTs safely, because a perfectly signed token still grants too much if its claims are not constrained and checked. Treat a JWT as a signed assertion whose trustworthiness depends entirely on correct verification at every hop, not as an opaque secret that is safe merely because it is long and unreadable.
Key Takeaways
- A JWT has three parts, header, payload, and signature, and the payload is only encoded, so it must not hold secrets.
- The most common flaws are the
algnone attack, algorithm confusion, weak HMAC secrets, and skipped claim checks. - Always pin the expected algorithm, verify the signature, and validate expiration, issuer, and audience.
- Use short-lived access tokens with revocable refresh tokens, since signed JWTs are otherwise hard to revoke.
- Store tokens according to your threat model and protect signing keys with rotation and key identifiers.