What Is a JWT Token? A Developer's Guide to JSON Web Tokens
Table of Contents
If you have built any web application in the last decade, you have almost certainly encountered JSON Web Tokens. They appear in authorization headers, get stored in cookies, and form the backbone of stateless authentication for APIs worldwide. Yet many developers use JWTs without fully understanding what happens beneath the surface. This guide breaks down every layer of a JWT so you can make informed architectural decisions and avoid the security mistakes that plague production systems.
What Exactly Is a JWT?
A JSON Web Token (JWT) is a compact, URL-safe string that represents a set of claims between two parties. Defined by RFC 7519, a JWT allows a server to encode information (such as a user's identity and permissions) into a token that the client can carry with every subsequent request. The server can later verify that token without querying a database, because the token itself contains all the information needed, plus a cryptographic signature that proves it has not been tampered with.
At its core, a JWT is just a string of three Base64URL-encoded segments separated by dots. It looks something like this:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkphbmUgRG9lIiwiaWF0IjoxNjE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
That looks like random gibberish at first glance, but each dot-separated segment has a specific purpose. Understanding those three parts is the key to understanding everything about JWTs.
The Three Parts of a JWT
Every JWT consists of a header, a payload, and a signature. Let's examine each one in detail.
1. The Header
The header is a JSON object that typically contains two fields: the signing algorithm being used (alg) and the token type (typ). When Base64URL-decoded, a typical header looks like this:
{
"alg": "HS256",
"typ": "JWT"
}
The alg field tells the recipient which cryptographic algorithm was used to create the signature. Common values include HS256 (HMAC with SHA-256), RS256 (RSA with SHA-256), and ES256 (ECDSA with P-256 curve). This field is critical to security, as we will discuss in the pitfalls section below.
2. The Payload (Claims)
The payload is where the actual data lives. It contains claims, which are statements about the user or any other entity. Claims fall into three categories:
- Registered claims are predefined by the JWT spec. They include
iss(issuer),sub(subject),aud(audience),exp(expiration time),nbf(not before),iat(issued at), andjti(JWT ID). None of these are mandatory, but they provide interoperability between different systems. - Public claims are defined by you but should be registered in the IANA JSON Web Token Claims registry (or use collision-resistant names like URIs) to avoid naming conflicts with other organizations.
- Private claims are custom key-value pairs agreed upon between the producer and consumer of the token. For example,
"role": "admin"or"org_id": "42".
A decoded payload might look like this:
{
"sub": "user_8374",
"name": "Jane Doe",
"role": "editor",
"iat": 1713272400,
"exp": 1713276000
}
One crucial thing to understand: the payload is encoded, not encrypted. Anyone who has the token can decode the payload and read its contents. Never store sensitive data like passwords, credit card numbers, or secret keys inside a JWT payload.
Decode JWTs Instantly
Paste any JWT into our decoder to see the header, payload, and signature verification status in real time. Useful for debugging authentication flows during development.
Open JWT Decoder →3. The Signature
The signature is what makes a JWT trustworthy. It is created by taking the encoded header, the encoded payload, a secret key (or private key), and the algorithm specified in the header, then running them through a cryptographic function. For HMAC-SHA256, the formula is:
HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secret )
When the server receives a JWT, it recalculates this signature using its own copy of the secret. If the recalculated signature matches the one in the token, the server knows two things: the token was created by someone who holds the secret, and the header and payload have not been modified since the token was signed. If even a single character of the payload changes, the signature will not match, and the server will reject the token.
How JWT Authentication Works
Understanding the token structure is important, but what really matters is how JWTs fit into an authentication flow. Here is the typical sequence for a web application:
- Login: The user sends credentials (username and password) to the server's login endpoint. The server verifies the credentials against its database.
- Token creation: If the credentials are valid, the server creates a JWT containing the user's ID, role, and an expiration timestamp. It signs the token with a secret key and sends it back to the client.
- Token storage: The client stores the JWT, typically in an HTTP-only cookie (preferred for web apps) or in memory (for SPAs that need it in the Authorization header).
- Authenticated requests: For every subsequent API call, the client includes the JWT, usually in the
Authorization: Bearer <token>header. - Token verification: The server extracts the JWT, verifies the signature, checks that it has not expired, and reads the claims. If everything checks out, the request proceeds.
The beauty of this system is that the server never needs to store session data. Every piece of information it needs to authenticate the user is contained within the token itself. This makes JWTs particularly well suited for distributed systems where multiple servers handle requests behind a load balancer.
Signing Algorithms: HMAC vs. RSA vs. ECDSA
The choice of signing algorithm has significant implications for your architecture and security posture.
HMAC (Symmetric)
HS256, HS384, HS512 use a single shared secret for both signing and verification. This is the simplest approach: the same key that creates the token also verifies it. HMAC is fast and works well when the token issuer and verifier are the same service. The downside is that any service that needs to verify tokens must also hold the signing secret, which means a compromise of any verifier compromises the entire system.
RSA (Asymmetric)
RS256, RS384, RS512 use a private key to sign and a public key to verify. This is ideal for microservice architectures where an authentication service signs tokens and many downstream services need to verify them. The downstream services only need the public key, which is safe to distribute widely. RSA keys are larger (2048+ bits) and signing is slower than HMAC, but verification is fast.
ECDSA (Asymmetric)
ES256, ES384, ES512 provide the same asymmetric benefits as RSA but with much smaller key sizes and faster operations. A 256-bit ECDSA key offers comparable security to a 3072-bit RSA key. This makes ECDSA popular for mobile and IoT applications where bandwidth and processing power are limited.
Understand Base64 Encoding
JWTs use Base64URL encoding for their header and payload segments. If you want to manually decode or encode JWT parts, our Base64 tool handles both standard Base64 and URL-safe variants.
Open Base64 Encoder/Decoder →When Should You Use JWTs?
JWTs are not a universal solution. They excel in specific scenarios and introduce unnecessary complexity in others.
Good Use Cases
- API authentication for stateless backends: When you have multiple servers behind a load balancer and want to avoid shared session stores, JWTs let any server verify a request independently.
- Microservice communication: An identity service can issue tokens that other services verify using only a public key, with no inter-service calls needed.
- Single sign-on (SSO): A centralized auth provider can issue JWTs that multiple applications across different domains accept.
- Short-lived authorization grants: Granting temporary, scoped access to a resource (for example, a pre-signed URL for file upload) where the permission details are embedded in the token.
Poor Use Cases
- Long-lived sessions with revocation needs: If you need to instantly revoke a user's access (ban a user, force logout), JWTs are difficult because they remain valid until expiration. You end up needing a blocklist, which partially negates the stateless advantage.
- Storing large amounts of data: Every claim adds to the token size, and the token travels with every request. A JWT with extensive permissions data can bloat headers and degrade performance.
- Simple monolithic applications: If your entire app runs on a single server with sticky sessions, a traditional server-side session stored in memory or Redis is simpler and easier to revoke.
Common Security Pitfalls
JWTs can be perfectly secure when implemented correctly, but the history of JWT vulnerabilities is long. Here are the most common traps developers fall into.
The "alg: none" Attack
The JWT specification allows for an "alg": "none" header, which means no signature at all. In the early days, many JWT libraries accepted unsigned tokens if the header said none. An attacker could simply remove the signature, set the algorithm to none, and modify the payload at will. Modern libraries reject alg: none by default, but you should always explicitly specify which algorithms your server accepts.
Algorithm Confusion Attacks
This attack exploits servers that use asymmetric algorithms (RS256) but don't properly validate the algorithm header. The attacker takes the server's RSA public key (which is often publicly available), creates a new token signed with HMAC using that public key as the HMAC secret, and sets the header to HS256. If the server naively reads the algorithm from the token header rather than enforcing a specific algorithm, it will try to verify the HMAC using the public key as the secret, and the signature will match. Always hardcode your expected algorithm on the server side.
Missing Expiration
Tokens without an exp claim live forever. If such a token is ever stolen, the attacker has permanent access. Always set an expiration, and keep it short (5 to 15 minutes for access tokens is common). Use a separate refresh token mechanism for long-lived sessions.
Storing Tokens in localStorage
Storing JWTs in the browser's localStorage makes them accessible to any JavaScript running on the page, including injected scripts from XSS vulnerabilities. A single XSS flaw means the attacker can steal every user's token. HTTP-only cookies are more resilient because JavaScript cannot read them, though they introduce CSRF concerns that require their own mitigations (SameSite attribute, CSRF tokens).
Not Validating Claims
Verifying the signature is necessary but not sufficient. You must also validate the exp (is the token expired?), iss (did the expected service issue this?), and aud (was this token intended for this service?). Skipping these checks opens the door to token reuse across unrelated services.
Explore Hashing Algorithms
JWT signatures rely on cryptographic hash functions like SHA-256. Experiment with different hashing algorithms and see how any change to input data produces a completely different hash output.
Open Hash Generator →Best Practices for Production
After understanding the pitfalls, here are the concrete steps you should take when implementing JWTs in a production system.
- Use short-lived access tokens with refresh tokens. Access tokens should expire in 5 to 15 minutes. When they expire, the client uses a long-lived refresh token (stored in an HTTP-only, Secure, SameSite cookie) to obtain a new access token. This limits the damage window if an access token is compromised.
- Hardcode the expected algorithm. Never trust the
algheader from the token itself. Configure your verification library to accept only the algorithm you chose, such asRS256. - Rotate signing keys regularly. Use key IDs (
kidin the header) to support multiple active keys during rotation. This allows you to sign new tokens with a new key while old tokens signed with the previous key remain valid until they expire. - Keep the payload minimal. Include only the claims you need: user ID, role, and expiration. Fetch additional data from the database when needed. This reduces token size and limits exposure if a token is leaked.
- Use HTTPS everywhere. JWTs sent over unencrypted connections can be intercepted. Enforce TLS for all endpoints that send or receive tokens.
- Implement token revocation for critical actions. Maintain a lightweight blocklist (in Redis, for example) of revoked token IDs (
jticlaims) for scenarios like password changes, account compromise, or explicit logout. Check this list during verification. - Validate all registered claims. Always check
exp,iss,aud, andnbfduring verification. Most JWT libraries have configuration options to enforce these checks automatically.
JWTs vs. Server-Side Sessions
The choice between JWTs and traditional sessions is not about which technology is superior. It is about which trade-offs suit your architecture.
Server-side sessions store a session identifier in a cookie and keep the actual session data on the server (in memory, a database, or Redis). The server has full control: it can revoke access instantly by deleting the session, it can update permissions without issuing a new token, and sensitive data never leaves the server. The downside is that every request requires a session lookup, and in distributed systems you need shared storage or sticky sessions.
JWTs push the session data to the client. The server does not need to store anything, which simplifies horizontal scaling. Any server can verify any request. However, you lose instant revocation, tokens can grow large, and a stolen token is valid until it expires.
Many production systems use a hybrid approach: JWTs for short-lived access tokens that authorize API calls, combined with server-side refresh token storage that allows revocation. This gives you the scalability of JWTs for the high-frequency access pattern while retaining control over long-lived sessions.
Encryption and Data Security
While JWTs are signed (not encrypted), there are situations where you need to encrypt sensitive payloads. Learn about symmetric and asymmetric encryption and how they protect data in transit.
Open Encryption Tool →Debugging and Inspecting JWTs
During development, you will frequently need to inspect token contents, verify signatures, and troubleshoot authentication failures. Here are practical tips for working with JWTs day-to-day.
Reading Token Contents
Since the header and payload are just Base64URL-encoded JSON, you can decode them with any Base64 decoder. Split the token at the dots, decode the first two segments, and you can read the claims directly. This is invaluable when debugging why an API returns 401: is the token expired? Is the audience claim wrong? Is the role missing?
Verifying Signatures Locally
For HMAC tokens, you can verify a signature by recalculating it with the secret and comparing. For RSA or ECDSA tokens, you need the public key. Most JWT libraries provide a verify function that does this automatically and returns either the decoded payload or an error.
Common 401 Causes
- Token expired: Check the
expclaim against the current Unix timestamp. Clock skew between servers can cause tokens to appear expired prematurely; most libraries support a small leeway (30 to 60 seconds). - Wrong audience: The
audclaim does not match what the server expects. This often happens when tokens are accidentally sent to the wrong microservice. - Algorithm mismatch: The server expects RS256 but the token was signed with HS256, or vice versa.
- Malformed token: The token string is missing a segment, has extra whitespace, or was URL-encoded twice. Check for encoding issues in your HTTP client.
Security Testing Checklist
When auditing a JWT implementation, test these scenarios:
- Send a token with
"alg": "none"and an empty signature. It should be rejected. - Modify the payload (change the role to admin) without updating the signature. It should be rejected.
- Send an expired token. It should be rejected.
- Send a token with a different
audclaim. It should be rejected. - If using asymmetric algorithms, try the algorithm confusion attack (sign with the public key as an HMAC secret). It should be rejected.
Work with JWTs Right in Your Browser
Decode, inspect, and verify JSON Web Tokens without installing anything. Paste a token, see the claims, and check the signature all in one place.
Open JWT DecoderWrapping Up
JSON Web Tokens are a powerful primitive for stateless authentication, but power comes with responsibility. The three-part structure of header, payload, and signature is elegantly simple, yet the security surface area is broad. Choose your signing algorithm based on your architecture (symmetric for monoliths, asymmetric for microservices). Keep tokens short-lived. Never trust the algorithm header from the token itself. Validate every claim. And store tokens in HTTP-only cookies whenever possible.
Understanding how JWTs work at a fundamental level, rather than treating them as magic strings from a library, puts you in a position to build authentication systems that are both scalable and secure. Whether you are building a small side project or an enterprise platform, the principles covered here will serve as a solid foundation for your auth layer.