JWT Authentication Explained: How Tokens Work in Modern APIs
JSON Web Tokens are the dominant authentication mechanism for modern web APIs, mobile apps, and microservices. Understanding how they work prevents the security mistakes that are hard to catch in code review.
Decode any JWT: Paste a token to see its header, payload, and claims โ with expiry displayed in human-readable format.
Open JWT Decoder โTable of Contents
The Three Parts
A JWT has three Base64URL-encoded parts separated by dots:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VyXzEyMyIsImV4cCI6OTk5OX0.SIGNATURE
[ HEADER ].[ PAYLOAD ].[ SIGNATURE ]
Header
{ "alg": "HS256", "typ": "JWT" }
alg specifies the signing algorithm: HS256 (HMAC with shared secret), RS256 (RSA with public/private keys), or ES256 (ECDSA). This determines how the token must be verified.
Payload
{
"sub": "user_123",
"name": "Alice Smith",
"role": "admin",
"iat": 1709488000,
"exp": 1709574400,
"iss": "https://auth.example.com",
"aud": "myapp"
}
Standard registered claims: sub (subject), iss (issuer), aud (audience), exp (expiry Unix timestamp), iat (issued at), nbf (not before). Custom claims like role or tenant_id are added by the application.
The payload is not encrypted โ only Base64URL-encoded. Anyone with the token can decode and read the claims. Never put passwords, credit card numbers, or private keys in a JWT payload.
Signature
HMACSHA256(
base64url(header) + "." + base64url(payload),
secret
)
The signature ensures that if anyone modifies the header or payload โ even a single character โ the signature won't match and the token will be rejected. This is the core security property.
The Authentication Flow
- Login: user sends credentials to the auth server
- Token issuance: auth server creates a signed JWT with user claims
- Storage: client stores the JWT (memory, HttpOnly cookie, or localStorage)
- Authenticated requests:
Authorization: Bearer <token>header on each request - Verification: API server verifies signature, checks
exp,aud,issโ no database lookup needed
Expiry and Refresh Tokens
JWTs are not revocable by default โ use short expiry times (15 min to 1 hour). Pair short-lived access tokens with long-lived refresh tokens (7โ30 days, stored in HttpOnly cookies) that are revocable for proper session management.
Common Security Mistakes
- Algorithm confusion: always specify the expected algorithm server-side โ never accept it from the token
- localStorage storage: vulnerable to XSS; use HttpOnly cookies for sensitive tokens
- Not validating
aud: a token for service A might be accepted by service B if audience isn't checked - Long-lived access tokens: increases exposure window for stolen tokens
JWT Security Pitfalls
The most dangerous JWT vulnerability is the alg: "none" attack. Some JWT libraries accept tokens with the algorithm set to "none" โ meaning no signature verification at all. An attacker can craft a token with any payload, set the algorithm to "none," and the server accepts it as valid. Always explicitly specify which algorithms your server accepts and reject tokens using any other algorithm, including "none."
A related attack involves confusing symmetric and asymmetric algorithms. If your server verifies tokens with an RSA public key, an attacker can create a token signed with HMAC using the public key as the secret. If the library doesn't distinguish between algorithm types, HMAC verification with the public key succeeds, and the attacker has forged a valid token. Modern libraries protect against this, but always verify that yours does by checking its security advisories.
Token expiration is not optional. Every JWT should have an exp claim, and your verification code should reject expired tokens. Without expiration, a stolen token remains valid forever. Set expiration to the shortest practical duration โ 15 minutes for access tokens is a common choice, with refresh tokens handling longer sessions. The JWT Decoder shows expiration status at a glance, which is useful when debugging authentication flows.
Refresh Token Architecture
Short-lived access tokens create a good security boundary, but forcing users to re-authenticate every 15 minutes creates a terrible experience. Refresh tokens solve this: when the access token expires, the client sends the refresh token to get a new access token without re-entering credentials. The refresh token is long-lived (days to weeks) but is only sent to one specific endpoint, reducing its exposure.
Critically, refresh tokens should be stored differently from access tokens. Access tokens are typically held in memory and sent as Bearer tokens in API requests. Refresh tokens should be stored in HttpOnly, Secure, SameSite cookies โ not in localStorage or sessionStorage, where they're vulnerable to XSS attacks. When a refresh token is used, the server should issue a new refresh token and invalidate the old one (rotation), so that a stolen refresh token can only be used once.
When Not to Use JWTs
JWTs are not the right choice for every authentication scenario. If you need the ability to instantly revoke a user's session (for example, when they change their password or an admin deactivates their account), JWTs are problematic because they're valid until they expire, regardless of server-side state. You'd need a token blacklist or revocation list, which partially negates the stateless advantage of JWTs in the first place.
For traditional server-rendered applications with sticky sessions, session cookies backed by a server-side session store (like Redis) are simpler, more secure, and just as performant. JWTs shine in microservice architectures where multiple services need to verify the same token without sharing a session store, and in single-page applications that communicate with multiple API backends.
JWT Security in 2026: RFC 8725 Best Practices
RFC 8725 (JSON Web Token Best Current Practices), published by the IETF, is the authoritative reference for secure JWT usage. Key recommendations:
- Explicitly whitelist acceptable algorithms. Never trust the
algheader โ an attacker can setalg: noneto bypass signature verification in naive implementations. Your server should reject any algorithm not on your approved list. - Prefer RS256 or ES256 over HS256 for public APIs where issuer and verifier are different systems. Asymmetric signing means the verifier never holds the private key.
- Keep access token lifetimes short โ 15 minutes is widely accepted. Use refresh tokens with rotation (issue a new refresh token on each use, invalidate the old one).
- Validate
iss,aud, andexpon every request. A token that passes signature verification but has an unexpected issuer or is already expired must still be rejected. - Store JWTs in
httpOnlycookies when possible. This prevents XSS-based theft โ JavaScript cannot readhttpOnlycookies. For SPAs that must uselocalStorage, XSS prevention becomes critical.
