What Is a JSON Web Token (JWT) and How Does It Work?
JSON Web Tokens have become the de facto standard for handling authentication in modern web applications and APIs. Whether you are building a single-page app, a mobile backend, or a microservices architecture, understanding how JWTs work is essential for securing your systems. In this comprehensive guide we break down the structure of a JWT, explain every standard claim, walk through encoding and signing, and discuss when to use JWTs versus traditional session cookies.
A Brief History of JWTs
Before JWTs became popular, most web applications relied on server-side sessions. A user would log in, the server would create a session object stored in memory or a database, and a session ID cookie would be sent back to the browser. While this approach works well for monolithic applications, it creates challenges in distributed environments where multiple servers need to share session state.
JSON Web Tokens were introduced as part of the JOSE (JSON Object Signing and Encryption) framework, formally specified in RFC 7519 (published in May 2015). The specification was authored by Michael Jones, John Bradley, and Nat Sakimura, building on earlier work in the OAuth 2.0 and OpenID Connect ecosystems. The goal was to provide a compact, URL-safe token format that could carry claims between two parties without requiring server-side storage.
Today, JWTs are used in virtually every modern authentication library and framework, from Auth0 and Firebase Authentication to Spring Security and Passport.js. Understanding their internals helps developers make informed decisions about token lifetimes, claim selection, and overall security posture.
The Three-Part Structure of a JWT
Every JWT consists of three parts separated by dots (.):
header.payload.signature
When you see a real token, it looks something like this:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
Each of those three segments is a base64url-encoded JSON string. Let us examine them one at a time.
1. The Header
The header is a small JSON object that describes the token type and the signing algorithm being used. A typical header looks like this:
{"alg": "HS256", "typ": "JWT"}
- alg — the signing algorithm, such as
HS256(HMAC-SHA256),RS256(RSA-SHA256), orES256(ECDSA-SHA256). - typ — the token type, almost always
"JWT".
Some headers also include a kid (key ID) field, which tells the verifier which key from a key set should be used to validate the signature. This is common in systems that rotate signing keys.
2. The Payload (Claims)
The payload is the heart of the JWT. It contains claims — statements about the user and additional metadata. Claims are categorized into three types:
- Registered claims — predefined by the JWT specification (RFC 7519) and recommended for interoperability.
- Public claims — defined by the application but registered in the IANA JSON Web Token Claims Registry to avoid collisions.
- Private claims — custom claims agreed upon between the producer and consumer of the token.
3. The Signature
The signature ensures that the token has not been tampered with. To create it, the server takes the encoded header and encoded payload, concatenates them with a dot, and signs the result using the algorithm specified in the header along with a secret key (for symmetric algorithms) or a private key (for asymmetric algorithms).
For example, using HMAC-SHA256:
HMACSHA256(base64urlEncode(header) + "." + base64urlEncode(payload), secret)
When a server receives a JWT, it recomputes this signature and compares it to the one in the token. If they match, the token is valid and has not been altered.
Understanding JWT Claims in Detail
The registered claims defined in RFC 7519 provide a standardized vocabulary. Here are the most important ones:
- iss (Issuer) — identifies the principal that issued the JWT. For example,
"iss": "https://auth.example.com". The receiving server should verify this matches the expected issuer. - sub (Subject) — identifies the principal that is the subject of the JWT. This is typically a user ID, such as
"sub": "user_42". - aud (Audience) — identifies the recipients the JWT is intended for. This can be a single string or an array:
"aud": ["https://api.example.com", "https://admin.example.com"]. Servers must reject tokens not intended for them. - exp (Expiration Time) — a Unix timestamp after which the token must be rejected. For example,
"exp": 1772006400means the token expires at a specific date and time. - nbf (Not Before) — a Unix timestamp before which the token must not be accepted. Useful for tokens issued in advance.
- iat (Issued At) — a Unix timestamp indicating when the token was created. This helps servers determine the token's age.
- jti (JWT ID) — a unique identifier for the token. This can be used to prevent replay attacks by ensuring the same token is not used twice.
In addition to registered claims, applications commonly include custom claims like roles, permissions, email, or tenant_id. Keep the payload lean — every extra byte increases the size of every HTTP request that carries the token.
Base64url Encoding Explained
JWTs use base64url encoding, which is a URL-safe variant of standard base64. The key differences are:
- The
+character is replaced with-(minus). - The
/character is replaced with_(underscore). - Padding characters (
=) are stripped.
This matters because JWTs are frequently transmitted in URL query parameters and HTTP headers, where standard base64 characters like +, /, and = can cause problems. Base64url encoding ensures the token is safe to include in any part of an HTTP message without additional escaping.
It is important to understand that base64url encoding is not encryption. Anyone who receives a JWT can decode the header and payload and read the claims. Never store sensitive information like passwords, credit card numbers, or private keys in a JWT payload.
Common Use Cases for JWTs
API Authentication
The most common use case for JWTs is API authentication. After a user logs in, the server issues a JWT that the client includes in the Authorization header of subsequent requests:
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
The server validates the token's signature and expiration on every request, extracts the user identity from the claims, and processes the request accordingly. Because the token is self-contained, the server does not need to look up a session in a database, making this approach highly scalable.
Single Sign-On (SSO)
JWTs are widely used in SSO implementations, particularly with protocols like OpenID Connect (OIDC). When a user authenticates with a central identity provider, the provider issues an ID token (which is a JWT) containing the user's identity claims. Relying party applications can verify this token and grant access without requiring the user to log in again.
The self-contained nature of JWTs makes them ideal for SSO across different domains and services, because the relying party only needs the issuer's public key to validate the token — no back-channel communication is required.
Information Exchange
Beyond authentication, JWTs can be used to securely transmit information between services. Because the token is signed, the receiver can trust that the content has not been modified in transit. This is useful in event-driven architectures, webhook payloads, and inter-service communication within microservices systems.
Password Reset and Email Verification
Many applications use JWTs as short-lived tokens for password reset links and email verification URLs. The token can contain the user's ID and an expiration time, eliminating the need to store reset tokens in the database. When the user clicks the link, the server verifies the token and processes the request.
JWT vs. Session Cookies: When to Use Which
The choice between JWTs and session cookies depends on your application architecture and requirements. Here is a comparison to help you decide:
- Statefulness: Session cookies are stateful — the server must store session data. JWTs are stateless — all data is in the token itself.
- Scalability: JWTs scale better in distributed systems because any server with the signing key can validate the token. Session cookies require shared session storage (Redis, database) across servers.
- Revocation: Session cookies are trivially revocable — just delete the session from the store. JWTs require additional infrastructure like blocklists for revocation.
- Size: Session cookies are small (just an ID). JWTs can grow large if many claims are included, adding bandwidth overhead to every request.
- Cross-domain: JWTs work easily across different domains and services. Cookies are bound to specific domains by default and require CORS configuration for cross-domain use.
- Mobile and API clients: JWTs are a natural fit for mobile apps and third-party API consumers that do not use cookies. Session cookies are tied to the browser's cookie jar.
For traditional server-rendered web applications with a single backend, session cookies are often simpler and more secure. For SPAs, mobile apps, microservices, and third-party API access, JWTs are typically the better choice.
Security Best Practices for JWTs
JWTs are only as secure as their implementation. Follow these best practices to avoid common pitfalls:
- Always validate the signature. Never trust a JWT without verifying its signature against the expected key and algorithm. Reject tokens with
"alg": "none"— this was a notorious vulnerability in early JWT libraries. - Use short expiration times. Access tokens should expire in minutes, not hours or days. Combine short-lived access tokens with longer-lived refresh tokens for a good balance of security and usability.
- Validate all claims. Check
iss,aud,exp, andnbfon every request. Do not skip claim validation just because the signature is valid. - Use strong signing keys. For HMAC algorithms, use a key that is at least 256 bits long and generated with a cryptographically secure random number generator. Never use simple strings like
"secret"in production. - Store tokens securely on the client. In browsers, store JWTs in
httpOnlycookies rather thanlocalStorageto prevent XSS attacks from accessing the token. If you must use localStorage, implement strict Content Security Policies. - Protect against CSRF. If you store JWTs in cookies, implement CSRF protection such as the SameSite cookie attribute or anti-CSRF tokens.
- Keep the payload small. Only include necessary claims. Large JWTs increase bandwidth usage and can exceed header size limits on some servers.
- Rotate signing keys periodically. Use key rotation with the
kidheader to allow graceful migration between old and new keys.
Signing Algorithms Compared
Choosing the right signing algorithm depends on your architecture:
- HS256 (HMAC-SHA256): Symmetric — the same secret is used to sign and verify. Fast and simple. Best when the same server (or a tightly coupled set of servers that share a secret) both issues and validates tokens.
- RS256 (RSA-SHA256): Asymmetric — a private key signs and a public key verifies. More computationally expensive but allows token verification without exposing the signing key. Ideal for microservices and third-party integrations.
- ES256 (ECDSA-SHA256): Asymmetric with elliptic curves. Produces smaller signatures than RSA with equivalent security strength. Great for bandwidth-constrained environments and modern systems.
- PS256 (RSASSA-PSS with SHA-256): A more secure padding scheme for RSA. Recommended over RS256 for new implementations when RSA is required.
Avoid the "none" algorithm entirely. Always explicitly specify which algorithms your server accepts and reject any tokens that do not match.
Decoding a JWT Step by Step
Let us walk through decoding a sample JWT manually. Consider this token:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwicm9sZSI6ImFkbWluIiwiaWF0IjoxNzA5ODU2MDAwLCJleHAiOjE3MDk4NTk2MDB9.signature_here
- Split on dots to get the three parts: header, payload, and signature.
- Decode the header from base64url:
{"alg":"HS256","typ":"JWT"} - Decode the payload from base64url:
{"sub":"1234567890","name":"John Doe","role":"admin","iat":1709856000,"exp":1709859600} - Verify the signature using the algorithm from the header and your secret key.
- Check expiration: compare the
expvalue to the current Unix timestamp.
You can use our JWT Decoder tool to instantly decode and inspect any JWT without writing code.
The Access Token and Refresh Token Pattern
In production systems, JWTs are typically used as part of an access token / refresh token pattern:
- The user authenticates with credentials (username/password, OAuth, etc.).
- The server issues a short-lived access token (e.g., 15 minutes) and a long-lived refresh token (e.g., 7 days).
- The client uses the access token for API requests.
- When the access token expires, the client sends the refresh token to a dedicated endpoint to obtain a new access token.
- If the refresh token is expired or revoked, the user must log in again.
This pattern provides a good balance: short-lived access tokens limit the window of exposure if a token is compromised, while refresh tokens provide a smooth user experience without requiring frequent re-authentication.
Frequently Asked Questions
Is a JWT encrypted?
Standard JWTs (JWS) are signed but not encrypted. The payload is base64url-encoded, which means anyone can decode and read its contents. If you need to hide the payload data, you should use a JSON Web Encryption (JWE) token or avoid putting sensitive information in the JWT payload altogether.
What happens when a JWT expires?
When a JWT's exp (expiration) claim timestamp is in the past, the server should reject the token and return a 401 Unauthorized response. The client must then obtain a new token, typically by re-authenticating or using a refresh token to get a fresh access token without requiring the user to log in again.
Can I revoke a JWT before it expires?
JWTs are stateless by design, so there is no built-in revocation mechanism. However, you can implement revocation by maintaining a server-side blocklist (deny list) of invalidated token identifiers, using short expiration times combined with refresh tokens, or storing a per-user token version that must match for the JWT to be accepted.
What is the difference between a JWT and a session cookie?
Session cookies store a session ID on the client while the actual session data lives on the server, making them stateful. JWTs are self-contained and carry all necessary data within the token itself, making them stateless. JWTs are ideal for distributed systems and APIs, while session cookies are simpler to manage and easier to revoke for traditional web applications.
Which signing algorithm should I use for JWTs?
For most applications, HS256 (HMAC with SHA-256) is sufficient when the same server both creates and validates tokens. For microservices or scenarios where different services need to verify tokens without possessing the signing key, use RS256 (RSA with SHA-256) or ES256 (ECDSA with SHA-256), which use asymmetric key pairs so the private key stays with the issuer.