If you have ever worked with modern web authentication, you have almost certainly encountered JSON Web Tokens. JWTs have become the de facto standard for transmitting identity and authorization data between services, powering everything from single-page applications to microservice architectures. In this comprehensive guide, we will break down exactly what a JWT is, how it is structured internally, how the authentication flow works end-to-end, and the security practices you need to follow to use JWTs safely in production.
What Is a JSON Web Token (JWT)?
A JSON Web Token (JWT, pronounced “jot”) is a compact, URL-safe token format defined by RFC 7519. It allows two parties to exchange claims — pieces of information — in a self-contained, verifiable package. Unlike opaque session tokens that require a server-side lookup, a JWT carries all the data it needs within itself.
JWTs matter because they enable stateless authentication. A server can issue a token, and any other server or service that shares the signing secret (or public key) can validate it without hitting a centralized session store. This property makes JWTs ideal for distributed architectures, microservices, single-page applications, and any system where multiple services need to independently verify a user's identity.
The three core properties that make JWTs so widely adopted are their compactness (small enough to send in HTTP headers or URL parameters), self-containment (the token itself holds all necessary information), and verifiability (the cryptographic signature guarantees integrity).
JWT Structure: Header, Payload, and Signature
Every JWT consists of three Base64URL-encoded parts separated by dots:
header.payload.signatureHere is what a real JWT looks like:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwicm9sZSI6ImFkbWluIiwiaWF0IjoxNTE2MjM5MDIyLCJleHAiOjE1MTYyNDI2MjJ9.POstGetfAytaZS82wHcjoTyoqhMyxXiWdR7Nn7A29DNSl0You can paste this token into the VelTools JWT Decoder to see each segment broken down instantly, with the header and payload decoded into readable JSON and the signature verification status shown in real time.
1. Header
The header is a JSON object that describes the token type and the signing algorithm being used. Once JSON-encoded, it is Base64URL-encoded to form the first segment of the token.
{
"alg": "HS256",
"typ": "JWT"
}The alg field specifies the cryptographic algorithm used to create the signature. Common algorithms include HS256 (HMAC with SHA-256, a symmetric algorithm), RS256 (RSA with SHA-256, an asymmetric algorithm), and ES256 (ECDSA with P-256 curve). The typ field is always set to "JWT". Choosing the right algorithm has significant implications for security and key management, which we will cover in the best practices section below.
2. Payload (Claims)
The payload contains the claims — statements about the user and additional metadata. Claims are key-value pairs in a JSON object, which is then Base64URL-encoded to form the second segment.
{
"sub": "1234567890",
"name": "John Doe",
"role": "admin",
"iat": 1516239022,
"exp": 1516242622
}It is critically important to understand that Base64URL encoding is not encryption. Anyone can decode the payload using a Base64 decoder. This means you should never place sensitive data such as passwords, credit card numbers, social security numbers, or personal secrets in the payload. The signature protects against tampering, but it does not protect against reading.
3. Signature
The signature is what makes JWTs trustworthy. It ensures that the token has not been tampered with after it was issued. The signature is created by taking the encoded header, the encoded payload, a secret key, and applying the algorithm specified in the header:
HMACSHA256(
base64UrlEncode(header) + "." + base64UrlEncode(payload),
secret
)When a server receives a JWT, it recomputes this signature using the same secret key and compares it to the signature in the token. If they match, the token is valid and has not been altered since it was signed. If even a single character of the header or payload has been changed, the signatures will not match and the token will be rejected.
Common JWT Claims
The JWT specification defines a set of registered claims. These are not mandatory but are widely recommended for interoperability across different systems and libraries:
iss(Issuer) — Identifies who issued the token, e.g."https://auth.example.com". Validating this claim ensures the token came from a trusted source.sub(Subject) — Identifies the principal that is the subject of the token, typically a user ID like"user_123".aud(Audience) — Identifies the intended recipients of the token, e.g."https://api.example.com". The receiving service should reject tokens not intended for it.exp(Expiration Time) — A Unix timestamp after which the token must not be accepted. This is the primary mechanism for limiting token lifetime.iat(Issued At) — A Unix timestamp indicating when the token was created. Useful for determining token age and for auditing purposes.nbf(Not Before) — A Unix timestamp before which the token must not be accepted. Useful for tokens that should only become valid at a future time.jti(JWT ID) — A unique identifier for the token. Can be used to prevent token replay attacks by tracking which token IDs have already been used.
Always validate exp, iss, and aud on the server side. Skipping these checks is one of the most common JWT security mistakes and can leave your application vulnerable to token misuse.
How JWT Authentication Works
Here is the typical end-to-end flow when using JWTs for API authentication:
- User logs in — The client sends credentials (username and password, or an OAuth code) to the authentication server over HTTPS.
- Server issues a JWT — After verifying credentials against the database, the server creates a JWT containing the user's identity and permissions, signs it with a secret key or private key, and returns it to the client.
- Client stores the JWT — The client saves the token. The recommended storage is an
httpOnlycookie, which is inaccessible to JavaScript and therefore protected against XSS attacks. Avoid usinglocalStorage. - Client sends JWT with each request — On subsequent API calls, the token is sent in the
Authorization: Bearer <token>header or automatically via the cookie. - Server validates the JWT — The server verifies the signature, checks the
expclaim to ensure the token has not expired, validates theissandaudclaims, and extracts user claims. No database lookup is needed for this step. - Access granted or denied — Based on the claims in the validated token (such as roles or permissions), the server allows or rejects the request.
This stateless approach scales exceptionally well because any server instance can verify the token independently. There is no need for sticky sessions or a shared session store, making JWTs a natural fit for load-balanced, containerized, and serverless environments.
JWTs vs. Session Tokens: When to Choose Which
JWTs and traditional server-side sessions each have strengths, and choosing between them depends on your architecture:
- Scalability: JWTs are stateless, so they scale horizontally without shared state. Session tokens require a centralized store (like Redis) to work across multiple servers.
- Revocation: Sessions can be instantly invalidated by deleting them from the server. JWTs remain valid until they expire, unless you implement a token blocklist — which partially negates the stateless benefit.
- Payload size: JWTs can grow large if you pack too many claims into them, increasing bandwidth usage on every request. Session tokens are just short identifiers.
- Cross-service verification: JWTs can be verified by any service that has the public key, making them ideal for microservice architectures. Session tokens require a call back to the session store.
For server-rendered applications with a single backend, session cookies are often the simpler and more secure option. For SPAs calling distributed APIs, microservice architectures, or systems requiring cross-domain authentication, JWTs typically shine.
JWT Security Best Practices
JWTs are powerful, but misusing them can introduce serious vulnerabilities. Follow these best practices to keep your implementation secure:
Always Use HTTPS
JWTs are bearer tokens — anyone who intercepts one can impersonate the user. Always transmit tokens over TLS/HTTPS to prevent man-in-the-middle attacks. Never send tokens over plain HTTP, even in development environments.
Keep Expiry Times Short
Set the exp claim to the shortest practical duration. For access tokens, 15 minutes is a widely adopted default. Use refresh tokens (stored securely on the server side) to issue new access tokens when they expire. Short-lived tokens limit the damage window if a token is leaked or stolen.
Store Tokens in httpOnly Cookies
Avoid storing JWTs in localStorage or sessionStorage, where they are vulnerable to cross-site scripting (XSS) attacks. Any JavaScript running on your page — including malicious third-party scripts — can read those storage mechanisms. Instead, use httpOnly, Secure, and SameSite=Strict cookies. The httpOnly flag prevents JavaScript from accessing the token entirely.
Validate the Algorithm
A well-known attack involves changing the alg header to "none" to bypass signature verification entirely. Another variant switches from RS256 to HS256, tricking the server into using the public key as a symmetric secret. Always enforce the expected algorithm on the server side. Never allow the token itself to dictate which algorithm to use without strict validation.
Use Strong Signing Keys
For HMAC-based algorithms like HS256, use a cryptographically random secret of at least 256 bits (32 bytes). For RSA algorithms, use 2048-bit or larger keys. Rotate keys periodically and use the kid (Key ID) header parameter to manage multiple active keys during rotation periods. Never hard-code signing secrets in your source code; use environment variables or a secrets manager.
Never Store Sensitive Data in the Payload
The payload is only Base64URL-encoded, not encrypted. Anyone can decode it with a simple Base64 decoder. Do not include passwords, API keys, personal identification numbers, or any data that would be harmful if exposed. If you need encrypted tokens, look into JWE (JSON Web Encryption, defined in RFC 7516).
Common JWT Mistakes to Avoid
Even experienced developers make these mistakes when working with JWTs. Watch out for these pitfalls:
- Not validating claims: Always check
exp,iss, andaud. Accepting any valid-signature token without claim validation opens the door to token misuse across different services. - Using JWTs for session management without a revocation strategy: If a user logs out or their account is compromised, you need a way to invalidate outstanding tokens. Implement a short-lived access token plus refresh token pattern, or maintain a token blocklist.
- Putting too much data in the token: Every claim increases token size, and the token is sent with every request. Keep the payload lean — include only what is needed for authorization decisions.
- Using weak or predictable secrets: Secrets like
"secret"or"password123"can be brute-forced in seconds. Always use cryptographically strong random values. - Not handling token expiration gracefully: Implement proper token refresh logic on the client side. Do not force users to re-login every time an access token expires.
- Ignoring the
algheader attack: Always allowlist the algorithms your server accepts. Reject tokens with unexpected algorithms immediately.
Decode and Inspect JWTs with VelTools
Whether you are debugging an authentication flow, auditing token contents during a security review, or simply learning how JWTs work, being able to quickly decode a token is essential. The VelTools JWT Decoder lets you paste any JWT and instantly see its decoded header, payload, and signature verification status — all processed directly in your browser with no data sent to any server.
Need to manually inspect the Base64-encoded segments of a token? Use our Base64 Encoder/Decoder to encode or decode individual parts and verify the raw data yourself.
Understanding JWTs deeply is one of the most valuable skills for any developer working with modern web applications. The next time you encounter a mysterious authentication failure or need to verify what claims a token carries, open the JWT Decoder, paste in the token, and get your answer in seconds. Bookmark it and keep it in your developer toolkit — you will use it more often than you think.