JWT Explained: JSON Web Tokens Guide
A comprehensive guide to understanding JSON Web Tokens (JWTs). Learn the structure, decode them using our free tool, or do it manually with code in JavaScript, Python, and other languages.
What is a JWT?
A JSON Web Token (JWT) is an open standard (RFC 7519) that defines a compact and self-contained way for securely transmitting information between parties as a JSON object. JWTs are commonly used for authentication and authorization in web applications and APIs. According to the Postman State of API Report, JWTs are the most widely used token format in modern API authentication, adopted by over 80% of OAuth 2.0 implementations.
How JWT Authentication Works
JWT authentication follows a stateless flow that eliminates the need for server-side session storage:
- User logs in with credentials (username/password, OAuth provider, etc.)
- Server validates credentials and creates a JWT containing user identity claims (sub, name, role)
- Server signs the JWT with a secret key (HMAC) or private key (RSA/ECDSA)
- Client stores the token (localStorage, sessionStorage, or httpOnly cookie)
- Client sends the JWT with every subsequent request via the Authorization header:
Bearer <token> - Server verifies the signature and expiry, then extracts user identity from the payload — no database lookup required
This stateless approach makes JWTs ideal for microservices architectures and distributed systems where a centralized session store would be a bottleneck or single point of failure.
JWT Structure
A JWT consists of three parts separated by dots (.):
header.payload.signature
Here is a real-world example:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
1. Header
Contains metadata about the token, including the type (JWT) and signing algorithm (HS256, RS256, etc.).
{"alg": "HS256", "typ": "JWT"}2. Payload
Contains claims — statements about the user and additional data. Standard claims include sub, exp, iat, iss.
{"sub": "1234567890", "name": "John Doe", "iat": 1516239022}3. Signature
Used to verify the token hasn't been tampered with. Created by signing the encoded header + payload with a secret key.
HMACSHA256( base64UrlEncode(header) + '.' + base64UrlEncode(payload), secret )
Standard JWT Claims (Registered Claims)
RFC 7519 defines a set of registered claim names that are not mandatory but recommended for interoperable JWTs:
| Claim | Full Name | Description |
|---|---|---|
| iss | Issuer | Identifies who issued the token |
| sub | Subject | Identifies the subject of the token (usually the user ID) |
| aud | Audience | Identifies the intended recipient of the token |
| exp | Expiration Time | Unix timestamp after which the token expires |
| nbf | Not Before | Unix timestamp before which the token is not valid |
| iat | Issued At | Unix timestamp when the token was issued |
| jti | JWT ID | Unique identifier for the token (prevents replay attacks) |
Common JWT Signing Algorithms
| Algorithm | Type | Use Case |
|---|---|---|
| HS256 | Symmetric (HMAC + SHA-256) | Single service — same secret signs and verifies |
| HS384 | Symmetric (HMAC + SHA-384) | Higher security symmetric signing |
| HS512 | Symmetric (HMAC + SHA-512) | Highest security symmetric signing |
| RS256 | Asymmetric (RSA + SHA-256) | Multiple services — private key signs, public key verifies |
| RS384 | Asymmetric (RSA + SHA-384) | Higher security asymmetric signing |
| RS512 | Asymmetric (RSA + SHA-512) | Highest security asymmetric signing |
| ES256 | Asymmetric (ECDSA + P-256) | Smaller signatures, mobile-friendly |
| ES384 | Asymmetric (ECDSA + P-384) | Higher security elliptic curve |
| ES512 | Asymmetric (ECDSA + P-521) | Highest security elliptic curve |
| EdDSA | Asymmetric (Edwards Curve) | Modern, fast, constant-time signing |
Method 1: Decode JWT Using Our Free Online Tool
The easiest way to inspect a JWT is using our browser-based JWT Decoder tool. It runs entirely in your browser — no data is sent to any server:
- Go to the JWT Decoder tool
- Paste your JWT token into the input field
- View the decoded header, payload, and signature instantly
- Copy individual claims or the full decoded JSON
Method 2: Decode JWT in JavaScript
You can decode JWT tokens programmatically in JavaScript using the built-in atob() function:
function decodeJWT(token) {
const parts = token.split(".");
if (parts.length !== 3) throw new Error("Invalid JWT");
const header = JSON.parse(atob(parts[0]));
const payload = JSON.parse(atob(parts[1]));
const signature = parts[2];
return { header, payload, signature };
}
// Example usage
const token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" +
".eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ" +
".SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c";
const decoded = decodeJWT(token);
console.log("Header:", decoded.header);
console.log("Payload:", decoded.payload);Method 3: Decode JWT in Python
Python requires handling base64 padding manually since the JWT format uses unpadded base64url encoding:
import json
import base64
def decode_jwt(token):
parts = token.split(".")
if len(parts) != 3:
raise ValueError("Invalid JWT")
def decode_part(part):
# Add padding: JWT uses unpadded base64url
padding = 4 - len(part) % 4
if padding != 4:
part += "=" * padding
return json.loads(base64.urlsafe_b64decode(part))
header = decode_part(parts[0])
payload = decode_part(parts[1])
signature = parts[2]
return header, payload, signature
token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
header, payload, signature = decode_jwt(token)
print(json.dumps(payload, indent=2))Method 4: Decode JWT in Node.js
In Node.js, use the built-in Buffer class which handles base64url natively:
function decodeJWT(token) {
const [headerB64, payloadB64] = token.split(".");
const decode = (str) =>
JSON.parse(Buffer.from(str, "base64url").toString("utf-8"));
return {
header: decode(headerB64),
payload: decode(payloadB64),
};
}
const jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...";
console.log(decodeJWT(jwt));How JWT Verification Works
Decoding a JWT only reads the base64-encoded data. Verification ensures the token has not been tampered with. The process differs by algorithm type:
Symmetric (HS256)
The server uses the same secret key to recompute the HMAC signature and compare it with the token's signature. If they match, the token is valid. This means only services that share the same secret can issue and verify tokens.
Asymmetric (RS256 / ES256)
The issuer signs with a private key. Any service with the corresponding public key can verify the signature without being able to issue new tokens. This is the preferred approach for microservices and third-party integrations.
JWT Security Best Practices
Following OWASP guidelines and industry standards, here are critical security practices for JWT implementation:
Never trust the payload
JWT payloads are base64-encoded, not encrypted. Anyone can decode them. Never store sensitive data like passwords or credit card numbers in a JWT payload.
Use HTTPS exclusively
Without HTTPS, JWTs can be intercepted during transmission (man-in-the-middle). Always serve your API and client over TLS.
Validate the signature
Always verify the signature server-side before trusting a JWT. For asymmetric algorithms, verify against the correct public key.
Set short expiration
Use the exp claim with short expiry windows (15-60 minutes). Implement refresh token rotation for long-lived sessions.
Validate all claims
Always check exp, nbf, iss, and aud claims server-side. Reject tokens with invalid or expired claims.
Rotate signing keys
Regularly rotate your signing keys. Use JWKS endpoints for dynamic key distribution in microservice environments.
Common JWT Attacks and Mitigations
| Attack | Description | Mitigation |
|---|---|---|
| None algorithm attack | Attacker changes the alg header to 'none' to bypass signature verification | Reject tokens with alg: 'none'. Always verify signatures. |
| Algorithm confusion | Attacker uses a public key as an HMAC secret to forge tokens on RS256-configured endpoints | Use separate validation logic per algorithm. Validate alg against an allowlist. |
| Key confusion | Attacker registers their own JWKS endpoint to inject malicious keys | Maintain an allowlist of trusted JWKS URLs. Validate key IDs. |
| Token replay | Stolen token reused to impersonate a user | Use short exp times. Implement jti claim with a server-side denylist. Use refresh token rotation. |
| Timing attack | Attacker measures response times to infer signature validation | Use constant-time comparison functions for signature verification. |
| Information disclosure | Sensitive data leaked through decoded payload | Never store secrets in the payload. Use JWE (JSON Web Encryption) if confidentiality is required. |
JWTs vs Alternative Authentication Approaches
| Approach | Stateful? | Scalability | Use Case |
|---|---|---|---|
| JWT (Bearer Token) | No (self-contained) | Excellent — no shared session store needed | Microservices, SPAs, mobile APIs |
| Session cookie | Yes (server-side) | Requires shared session store (Redis, DB) | Traditional server-rendered web apps |
| API Key | No (static) | Good — simple but less secure | Service-to-service, third-party integrations |
| SAML assertion | No (XML token) | Good — XML-based, enterprise-focused | Enterprise SSO, legacy systems |
| OAuth 2.0 + JWT | No (composite) | Excellent — standardized delegation | Third-party auth, delegated access |
Real-World JWT Usage Example
Here is a complete example of how a React application authenticates with a JWT-based API:
// Login: authenticate and store the JWT
async function login(email, password) {
const res = await fetch("https://api.example.com/auth/login", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ email, password }),
});
const { token } = await res.json();
// Store token (httpOnly cookie is more secure)
localStorage.setItem("jwt", token);
return token;
}
// Authenticated request: send JWT in Authorization header
async function fetchUserProfile() {
const token = localStorage.getItem("jwt");
const res = await fetch("https://api.example.com/users/me", {
headers: {
"Authorization": `Bearer ${token}`,
},
});
if (res.status === 401) {
// Token expired — redirect to login
window.location.href = "/login";
return null;
}
return res.json();
}Frequently Asked Questions
What is the difference between JWT encoding and encryption?
JWT uses base64url encoding, which is reversible — anyone can decode the payload without a key. Encryption (JWE) makes the payload unreadable without a decryption key. Most JWTs are encoded, not encrypted.
Can a JWT be revoked before it expires?
JWTs are stateless, so they cannot be revoked server-side without maintaining a denylist. For scenarios requiring immediate revocation, use short expiration times combined with refresh token rotation, or maintain a token denylist in Redis.
What is the maximum size of a JWT?
There is no hard limit in the specification, but most HTTP servers and proxies limit header sizes to 8-16 KB. Since JWTs are sent in the Authorization header, keeping them under 4 KB is recommended for performance.
Should I store JWT in localStorage or cookies?
Cookies (with httpOnly and Secure flags) are more secure because they prevent JavaScript access, mitigating XSS attacks. localStorage is simpler but vulnerable to XSS. The best practice is using httpOnly cookies with CSRF protection.
What is a refresh token and why do I need one?
A refresh token is a long-lived credential used to obtain new access tokens without requiring the user to re-authenticate. This allows short-lived JWTs (15-60 minutes) while maintaining persistent sessions.