JWT Tokens Explained — A Developer's Guide
Understand JSON Web Tokens (JWT): structure, claims, signing algorithms, when to use them, and critical security considerations. A practical guide for developers.
What Is a JWT?
A JSON Web Token (JWT, pronounced "jot") is a compact, URL-safe way to represent claims between two parties. In practice, JWTs are most commonly used for authentication — after a user logs in, the server issues a JWT that the client includes in subsequent requests to prove their identity.
Unlike traditional session-based authentication where the server stores session data, JWTs are self-contained. The token itself carries all the information needed to verify the user's identity and permissions. The server doesn't need to look up anything in a database to validate a JWT — it just verifies the signature.
This stateless property is what makes JWTs popular in distributed systems, microservice architectures, and APIs where maintaining server-side session state would be complex or impractical. A JWT issued by an auth service can be verified by any service that has the signing key.
JWT Structure: Header, Payload, Signature
A JWT consists of three Base64URL-encoded parts separated by dots: header.payload.signature
Header — Contains metadata about the token, specifically the signing algorithm and token type:
{"alg": "HS256", "typ": "JWT"}
Common algorithms are HS256 (HMAC with SHA-256, symmetric) and RS256 (RSA with SHA-256, asymmetric). The choice affects how tokens are signed and verified.
Payload — Contains the claims (the actual data). This is where you put user identity, permissions, expiration time, and any other data the receiving service needs:
{"sub": "user123", "name": "Jane Doe", "role": "admin", "iat": 1735689600, "exp": 1735776000}
The payload is Base64URL-encoded, NOT encrypted. Anyone can decode it and read the contents. Never put secrets (passwords, API keys, SSNs) in a JWT payload.
Signature — Created by signing the encoded header and payload with a secret key (HS256) or private key (RS256). The signature ensures the token hasn't been tampered with. If anyone changes a single character in the header or payload, the signature verification will fail.
Standard and Custom Claims
JWT defines a set of registered claims (standardized names with agreed-upon meanings) and allows any custom claims you need:
Registered claims:
iss(Issuer) — Who issued the token. Usually your auth service's URL.sub(Subject) — Who the token is about. Usually the user ID.aud(Audience) — Who the token is intended for. Prevents tokens meant for one service from being used on another.exp(Expiration) — Unix timestamp when the token expires. After this time, the token must be rejected.iat(Issued At) — Unix timestamp when the token was created.nbf(Not Before) — Token is not valid before this timestamp.jti(JWT ID) — Unique identifier for the token, useful for preventing replay attacks.
Custom claims can be anything your application needs: role, permissions, org_id, plan, etc. Keep payloads small — they're included in every request header. Store only the minimum data needed for authorization decisions.
When to Use JWTs (and When Not To)
JWTs are excellent for:
- API authentication — Stateless tokens that don't require server-side session storage. Ideal for REST and GraphQL APIs.
- Microservice authorization — One auth service issues the token; all other services can verify it independently without calling back to the auth service.
- Single Sign-On (SSO) — JWTs can carry identity information across different applications and domains.
- Short-lived tokens — Access tokens with 15-60 minute expiration that grant temporary access to resources.
JWTs are NOT ideal for:
- Long-lived sessions — If you need to revoke access immediately (user banned, password changed), JWTs can't be invalidated before expiration without maintaining a blocklist, which negates their stateless advantage.
- Storing sensitive data — JWTs are signed, not encrypted. The payload is readable by anyone. Use JWE (JSON Web Encryption) if payload confidentiality matters.
- Simple applications — A traditional web app with a single server doesn't need JWTs. Server-side sessions with cookies are simpler and more secure for that use case.
Security Considerations
JWTs are a frequent source of security vulnerabilities, usually from implementation mistakes rather than flaws in the spec. Here are the critical rules:
- Always verify the signature — Never trust a JWT without verifying its signature. This sounds obvious but some implementations skip verification in certain code paths.
- Check the algorithm — The notorious "alg: none" attack works because some libraries accept tokens with no signature if the header says
"alg": "none". Always reject tokens with unexpected algorithms. - Validate exp, iss, and aud — Always check that the token hasn't expired, was issued by your auth service, and is intended for your service. Skipping any of these opens attack vectors.
- Use short expiration times — Access tokens should expire in 15-60 minutes. Use refresh tokens (stored securely) to get new access tokens without re-authentication.
- Store tokens securely — In browsers, httpOnly cookies are safer than localStorage because they're not accessible to JavaScript (preventing XSS attacks from stealing tokens).
- Don't put sensitive data in the payload — The payload is Base64-encoded, not encrypted. Treat it as public information.
When in doubt, use a well-maintained library for JWT creation and verification rather than implementing it yourself. The spec has subtle security requirements that are easy to get wrong.
Frequently Asked Questions
›Can I decode a JWT without the secret key?
Yes, the header and payload are Base64URL-encoded, not encrypted. Anyone can read them. The secret key is only needed to verify the signature (proving the token wasn't tampered with).
›What's the difference between HS256 and RS256?
HS256 uses a shared secret (symmetric) — the same key signs and verifies. RS256 uses a private/public key pair (asymmetric) — the private key signs, any service with the public key can verify. RS256 is better for distributed systems.
›How do I revoke a JWT before it expires?
You can't directly — that's the trade-off of stateless tokens. Common solutions: short expiration times (15 min), token blocklists checked on verification, or token versioning tied to the user record.
›Should I store JWTs in localStorage or cookies?
HttpOnly cookies are more secure because JavaScript can't access them, preventing XSS attacks from stealing tokens. localStorage is simpler but vulnerable to XSS. For security-sensitive apps, use httpOnly cookies.
No signup. Runs in your browser.