JWT Authentication Explained: Tokens, Claims & Security
Understand JSON Web Tokens: structure, claims, signing algorithms, refresh tokens, security best practices, and common mistakes in JWT authentication.
- Understand JSON Web Tokens: structure, claims, signing algorithms, refresh tokens, security best practices, and common mistakes in JWT authentication.
- What Is a JWT?.
- Covers jwt structure: header, payload, signature.
- Covers standard jwt claims.
- Covers signing algorithms.
What Is a JWT?
A JSON Web Token (JWT, pronounced "jot") is a compact, URL-safe token format for transmitting claims between two parties. It's the most widely used format for API authentication and authorization in modern web applications.
A JWT is self-contained โ the token itself carries all the information needed to verify a user's identity and permissions. The server doesn't need to look up a session in a database. It just validates the token's signature and reads the claims. This stateless nature makes JWTs ideal for distributed systems and microservice architectures.
JWT Structure: Header, Payload, Signature
A JWT consists of three Base64URL-encoded parts separated by dots:
Header โ contains the token type (`JWT`) and the signing algorithm (e.g., `HS256`, `RS256`).
Payload โ contains the claims: key-value pairs with user identity, permissions, and metadata. Claims are readable by anyone โ the payload is encoded, not encrypted.
Signature โ created by signing the encoded header and payload with a secret key (for HMAC) or a private key (for RSA/ECDSA). This is what prevents tampering โ if any part of the token changes, the signature won't match.
You can decode and inspect any JWT with the JWT Decoder tool. Paste a token and instantly see the header, payload, and signature status.
Standard JWT Claims
The JWT specification defines several registered claims (standardized field names):
background-size animation or @property registered custom properties instead.`sub` (subject) โ who the token is about, typically a user ID.
`iss` (issuer) โ who created and signed the token.
`aud` (audience) โ who the token is intended for (e.g., a specific API).
`exp` (expiration time) โ when the token expires, as a Unix timestamp.
`iat` (issued at) โ when the token was created.
`nbf` (not before) โ the token shouldn't be accepted before this time.
`jti` (JWT ID) โ a unique identifier for the token, useful for preventing replay attacks.
You can also add custom claims for roles, permissions, or any application-specific data. Keep the payload lean โ every extra byte increases the token size, and JWTs are sent with every request.
Signing Algorithms
HS256 (HMAC + SHA-256) โ symmetric signing. The same secret key signs and verifies the token. Simple and fast, but the secret must be shared between the issuer and every service that verifies tokens โ a single compromised key means all tokens can be forged.
RS256 (RSA + SHA-256) โ asymmetric signing. A private key signs the token; a separate public key verifies it. Services only need the public key, which is safe to distribute. Ideal for microservice architectures where many services verify tokens but only one issues them.
ES256 (ECDSA + SHA-256) โ asymmetric signing with smaller keys and faster verification than RSA. Increasingly popular for performance-sensitive applications.
Never use none as an algorithm. The none algorithm means the token is unsigned โ any attacker can forge tokens. Many JWT libraries have historically accepted none by default, which has caused serious security breaches.
JWT Authentication Flow
The typical JWT authentication flow works like this:
1. The client sends credentials (username/password) to the authentication endpoint.
2. The server verifies credentials, generates a JWT with the user's identity and permissions, and returns it.
3. The client stores the token (usually in memory or an httpOnly cookie) and includes it in the Authorization header of subsequent requests: Authorization: Bearer .
4. The server validates the token's signature and expiration on every request, then extracts the user's identity from the claims.
Because the token contains all necessary information, the server doesn't need to query a session store. This makes authentication stateless and horizontally scalable.
Refresh Tokens
Access tokens should have short lifetimes (5โ15 minutes) to limit damage if they're stolen. But asking users to re-authenticate every 15 minutes is terrible UX. Refresh tokens solve this.
A refresh token is a long-lived token (days or weeks) that can only be used to request a new access token. It's stored in an httpOnly cookie and sent only to the token refresh endpoint โ never to API endpoints. When the access token expires, the client sends the refresh token to get a new access token without user interaction.
Refresh tokens should be single-use (rotate on every refresh) and stored in a database so they can be revoked. This gives you the stateless benefits of JWTs for API access while maintaining server-side control over session lifetime.
Security Best Practices
Keep tokens short-lived. Access tokens should expire in minutes, not hours or days. Use refresh tokens for session continuity.
Store tokens securely. In browsers, httpOnly cookies are safest โ they're inaccessible to JavaScript, which prevents XSS-based token theft. If you must use localStorage, understand that any XSS vulnerability in your app can steal tokens.
Validate everything. Always verify the signature, check `exp` and `nbf` claims, validate the `iss` and `aud` claims, and reject tokens with unexpected algorithms.
Don't put sensitive data in the payload. JWTs are encoded, not encrypted. Anyone can decode the payload with a Base64 decoder. Never include passwords, credit card numbers, or secrets.
Use strong keys. For HS256, use at least 256 bits of random data. For RSA, use 2048-bit keys minimum. Rotate keys periodically.
Common JWT Mistakes
Storing JWTs in localStorage. Any XSS vulnerability gives attackers full access to the token. Use httpOnly cookies instead.
Making tokens too large. JWTs are sent with every request. Including large permission objects, full user profiles, or nested data bloats bandwidth and header size. Keep payloads minimal.
Not validating the algorithm. Some libraries allow the `alg` header to override the expected algorithm. An attacker could change `RS256` to `HS256` and sign with the public key (which isn't secret). Always enforce the expected algorithm server-side.
Never expiring tokens. A token without an `exp` claim is valid forever if stolen. Always set expiration.
When Not to Use JWTs
JWTs aren't ideal for every authentication scenario. If you need immediate session revocation (e.g., "log out all devices now"), server-side sessions are simpler โ you just delete the session record. With JWTs, you need a token blacklist or very short expiration times.
For simple server-rendered applications with a single backend, session cookies are battle-tested and simpler. JWTs add complexity without benefit when you're not building a distributed system.
JWTs shine in API authentication, microservice architectures, single sign-on (SSO), and mobile app authentication where stateless verification across multiple services is a genuine requirement.