What is a JWT?
How JSON Web Tokens work — structure, claims, signing algorithms, and how to use them safely for authentication.
What is a JWT?
A JWT (JSON Web Token, pronounced "jot") is an open standard defined in RFC 7519 for securely transmitting information as a compact, self-contained JSON object. Introduced around 2013, JWTs quickly became the dominant approach for stateless authentication in REST APIs and single-page applications.
Three properties define a JWT:
- Compact — the token is URL-safe and small enough to be sent in an HTTP header, query string, or cookie without bloating requests.
- Self-contained — the token carries all the information needed about the user or session directly inside it. No round-trip to a database or session store is required to understand who the token belongs to.
- Verifiable — the token is cryptographically signed. Anyone with the correct key can confirm the token has not been tampered with since it was issued.
JWTs are most commonly used as access tokens in the OAuth 2.0 and OpenID Connect flows, but they also appear in refresh tokens, email verification links, password reset links, and any other scenario where you need to pass a verifiable, short-lived claim between systems.
The three-part structure
A JWT is made up of three Base64URL-encoded sections separated by dots:
header.payload.signature
In practice, a real token looks like this:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9 .eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ .SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
Each section is independently Base64URL-encoded — a URL-safe variant of Base64 that uses - and _ instead of + and / — so the full token can be safely placed in a URL or HTTP header without additional encoding.
The header
The header is a small JSON object that describes the token itself — specifically which algorithm was used to sign it and that this is a JWT:
{
"alg": "HS256",
"typ": "JWT"
}The alg field tells the verifier which algorithm to use when checking the signature. The two most common values are:
- HS256 — HMAC using SHA-256. A symmetric algorithm: the same secret key is used to both sign and verify the token. Simple to set up, but every service that needs to verify tokens must also know the secret, which limits how safely you can share verification across services.
- RS256 — RSA using SHA-256. An asymmetric algorithm: the private key signs the token and the public key verifies it. The public key can be freely distributed — even published as a JWKS (JSON Web Key Set) endpoint — without exposing signing capability. Preferred for production systems.
The payload (claims)
The payload is the heart of the token — a JSON object containing claims, which are statements about an entity (usually the authenticated user) and additional metadata. There are three categories:
- Registered claims — a predefined set of standard fields:
iss(issuer),sub(subject / user ID),aud(audience),exp(expiration Unix timestamp),iat(issued at), andnbf(not before). These are optional but strongly recommended. - Public claims — custom fields defined and agreed upon between the parties using the token, typically namespaced to avoid collisions.
- Private claims — arbitrary custom data specific to your application: roles, permissions, feature flags, tenant IDs, and so on.
A realistic payload for an API access token might look like this:
{
"sub": "user_abc123",
"name": "Jane Smith",
"email": "jane@example.com",
"role": "admin",
"iat": 1716825600,
"exp": 1716912000
}Important: the payload is not encrypted — it is only Base64URL-encoded, which anyone can decode in seconds. JWTs are signed for integrity, not confidentiality. Never store passwords, payment details, secrets, or other sensitive data in the JWT payload.
The signature
The signature is what makes a JWT trustworthy. It is created by taking the Base64URL-encoded header, a dot, and the Base64URL-encoded payload, then running that string through the signing algorithm with the secret (or private key):
HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secret )
The resulting signature is appended as the third section of the token. When a server receives a JWT, it recomputes the signature from the header and payload using the same key. If the recomputed signature matches the one in the token, the token is authentic and unmodified. If even a single character of the header or payload was changed after signing, the check fails.
This is the critical distinction: the signature proves integrity, not confidentiality. An attacker can read every claim in your token, but they cannot forge a valid signature without knowing your secret key.
How JWT authentication works
The standard JWT authentication flow follows these steps:
- 1The user submits their credentials (username and password) to the login endpoint.
- 2The server verifies the credentials against the database. If valid, it creates a JWT signed with the server's secret key and sets the expiration claim (exp).
- 3The server returns the JWT to the client in the response body (or a Set-Cookie header for cookie-based storage).
- 4The client stores the token — typically in an HttpOnly cookie or in memory for sensitive applications.
- 5On subsequent requests, the client sends the token in the Authorization header: Authorization: Bearer <token>.
- 6The server receives the request, verifies the signature, checks the exp claim, and if both pass, processes the request — without a single database lookup.
Step 6 is the key advantage: because the token is stateless and self-contained, the server does not need to query a session store or database to authenticate a request. This makes JWTs well-suited for high-throughput APIs, microservice architectures, and horizontally scaled systems where multiple server instances need to validate the same tokens.
JWT vs session tokens
JWTs and server-side session tokens are both valid approaches to authentication. They make different trade-offs:
Session tokens (stateful)
The server stores session data — user ID, roles, expiry — in a database or cache like Redis. The client holds only an opaque session ID. Revocation is trivial: delete the session record and the token is immediately invalid. The cost is a database lookup on every authenticated request.
JWTs (stateless)
All state lives in the token itself. No database lookup needed for verification. The cost is that revocation is hard — you cannot invalidate a token before its exp without maintaining a denylist, which reintroduces state.
Prefer sessions when:
- Building traditional server-rendered web apps
- You need instant token revocation (logout, ban)
- User data changes frequently (roles, plan tier)
Prefer JWTs when:
- Building APIs or microservices
- Cross-domain or cross-service authentication
- Mobile apps or stateless infrastructure
Security considerations
JWTs have a reputation for being easy to misuse. These are the most important things to get right:
Always verify the signature
Never decode a JWT and use its payload without first verifying the signature. Decoding is trivial and requires no key — verification is the part that matters. Use a JWT library that handles verification and always check for errors in the return value.
Always check exp
A valid signature does not mean a usable token. Always reject tokens where the current time is past the exp claim. Good JWT libraries do this automatically — verify this behavior is not disabled in your configuration.
Use short expiry times
Access tokens should typically expire in 15 minutes to 1 hour. Shorter windows limit the damage if a token is stolen. Pair short-lived access tokens with longer-lived refresh tokens (hours to days) used only to obtain new access tokens.
Never put sensitive data in the payload
The payload is Base64URL-encoded — not encrypted. Anyone who intercepts or extracts the token can read every claim. Keep only what is needed for authorization decisions: a user ID, roles, and standard registered claims.
Prefer RS256 in production
With HS256, every service that verifies tokens must know the secret — a leaked secret compromises the entire system. RS256 lets you distribute the public key freely while keeping the signing private key tightly controlled on the auth server.
The alg: none attack
The JWT spec permits an algorithm value of "none", meaning no signature is required. Naive implementations that accept any algorithm from the token header can be tricked into accepting unsigned tokens. Always explicitly specify which algorithms your application accepts and never allow none.
Frequently asked questions
- Is the JWT payload encrypted?
- No. The payload is only Base64URL encoded, which anyone can decode in a single line of code. JWTs are signed for integrity — to prove the token wasn't tampered with — not encrypted for confidentiality. If you need to encrypt the payload so that only the intended recipient can read it, use JWE (JSON Web Encryption) instead.
- How do I invalidate a JWT before it expires?
- You can't easily — that's a fundamental trade-off of stateless tokens. The most practical approaches are: keep access token expiry very short (15 minutes) so stolen tokens quickly become useless; maintain a token blacklist or denylist in a fast store like Redis that is checked on each request; or use refresh token rotation with token family tracking to detect and block reuse of revoked refresh tokens.
- Where should I store JWTs on the client?
- HttpOnly cookies are the safest option. JavaScript cannot access HttpOnly cookies at all, which eliminates the XSS risk entirely. localStorage is convenient but any XSS vulnerability in your app exposes every stored token. sessionStorage is slightly better — tokens are cleared when the tab closes — but still accessible to JavaScript. For highly sensitive applications, storing the access token only in memory (a JavaScript variable) and using an HttpOnly cookie for the refresh token is a solid pattern.
- What is the difference between HS256 and RS256?
- HS256 uses a single shared secret key for both signing and verification (symmetric). Any party that can verify a token can also forge one, so the secret must be tightly guarded. RS256 uses a private key to sign and a mathematically related public key to verify (asymmetric). You can publish the public key widely — as a JWKS endpoint, for example — without granting anyone the ability to forge tokens. RS256 is generally preferred for production APIs, especially those consumed by multiple services.