JSON Web Token (JWT)
A JSON Web Token (JWT) is a compact, URL-safe open standard (RFC 7519) used to securely transmit verifiable claims between distributed web services.
A JSON Web Token (JWT) is an open, industry-standard specification (RFC 7519) that defines a compact, URL-safe mechanism for transmitting digitally verifiable claims between client applications and backend servers using structured JSON objects. JWTs are stateless and self-contained, encapsulating user identity, permissions, and expiration dates directly inside the token payload, eliminating the need for database session lookups on every request.
You can inspect, decode, and debug any token payload in real-time with our client-side JWT Decoder tool.
Anatomy of a JWT: Three Concatenated Segments
A JWT appears as three distinct Base64URL-encoded strings separated by literal periods (.):
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkFsaWNlIiwiYWRtaW4iOnRydWUsImV4cCI6MTcxNTI0MTYwMH0.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
└───────────────────┬──────────────────┘ └─────────────────────────┬────────────────────────┘ └──────────────────────────┬──────────────────────────┘
1. Header 2. Payload 3. Signature
1. Header (Metadata)
Identifies the cryptographic algorithm (alg) and token type (typ):
{
"alg": "HS256",
"typ": "JWT"
}
2. Payload (Claims)
Contains the assertions about the entity (user) and auxiliary session data. Claims are categorized into:
- Registered Claims: Predefined, standardized keys recommended by RFC 7519.
- Public Claims: Custom collision-resistant claims defined in public registries or URIs.
- Private Claims: Custom agreed-upon keys shared between producer and consumer systems.
3. Signature (Tamper Resistance)
The signature is generated by hashing the Base64URL-encoded header and payload with a secret key or private key:
Signature = HMACSHA256(
base64UrlEncode(header) + "." + base64UrlEncode(payload),
secretKey
)
Standard Registered Claims (RFC 7519)
| Claim Key | Full Name | Type | Purpose & Verification Rule |
|---|---|---|---|
sub |
Subject | String | Unique identifier for the authenticated user or service principal. |
iss |
Issuer | String | Identifies the identity provider or auth server that minted the token. |
aud |
Audience | String/Array | Target recipient or API resource servers allowed to accept the token. |
exp |
Expiration Time | NumericDate | Unix timestamp after which the token must be rejected. |
nbf |
Not Before | NumericDate | Unix timestamp before which the token must not be accepted. |
iat |
Issued At | NumericDate | Unix timestamp recording when the auth server issued the token. |
jti |
JWT ID | String | Unique nonce for the token; used to prevent replay attacks and facilitate blacklisting. |
Signing Algorithms: HS256 vs RS256 vs ES256
| Feature | HS256 (HMAC-SHA256) | RS256 (RSA Signature) | ES256 (ECDSA P-256) |
|---|---|---|---|
| Cryptography Type | Symmetric (Shared Secret) | Asymmetric (Public/Private Key) | Asymmetric (Elliptic Curve) |
| Signing Key | Shared Secret | Private RSA Key (2048+ bits) | Private Elliptic Curve Key |
| Verification Key | Same Shared Secret | Public RSA Key (JWKS) | Public EC Key (JWKS) |
| Performance | Ultra-Fast | Moderate | Fast |
| Signature Size | 32 bytes (Compact) | 256–512 bytes (Large) | 64 bytes (Compact) |
| Optimal Architecture | Single monolithic backend | Distributed Microservices / Auth0 / Okta | High-scale mobile & IoT APIs |
Critical Security Vulnerabilities & Developer Mitigations
1. The alg: none Vulnerability
Early flawed JWT libraries allowed malicious clients to alter the header to {"alg": "none"} and strip the signature entirely. If unpatched, backend servers accepted the forged token as valid.
- Mitigation: Strictly whitelist allowed algorithms on the server (e.g.,
algorithms: ['RS256']) and reject any token claimingnone.
2. Algorithm Confusion Attacks
In systems utilizing asymmetric RS256 keys, attackers take the server's public key (often published openly via .well-known/jwks.json), construct a rogue token, set alg: "HS256", and sign it using the public key as the HMAC secret! If the backend verification routine blindly trusts the header algorithm, it verifies the token using its public key as a symmetric HMAC key.
- Mitigation: Hardcode the expected algorithm during verification; never dynamically derive the algorithm from the untrusted token header.
3. Token Storage: LocalStorage vs HttpOnly Cookies
| Storage Location | XSS Protection | CSRF Protection | Recommendation |
|---|---|---|---|
localStorage / sessionStorage |
Vulnerable (Any injected script can steal tokens) | Immune | Avoid for sensitive production auth. |
HttpOnly Cookie (SameSite=Strict, Secure) |
Protected (JavaScript cannot access cookie) | Protected via SameSite |
Recommended standard for web apps. |
| In-Memory Variable | Protected against persistence | Immune | Best for short-lived SPA access tokens paired with refresh cookies. |
Code Example: Verifying a JWT with Node.js Crypto
import crypto from 'node:crypto';
export function verifyHs256Jwt(token, secret) {
const parts = token.split('.');
if (parts.length !== 3) throw new Error('Invalid token structure');
const [headerB64, payloadB64, signatureB64] = parts;
// 1. Recompute expected signature using HMAC-SHA256
const data = `${headerB64}.${payloadB64}`;
const expectedSig = crypto
.createHmac('sha256', secret)
.update(data)
.digest('base64url');
// 2. Timing-safe comparison to prevent timing attacks
const isValidSig = crypto.timingSafeEqual(
Buffer.from(signatureB64),
Buffer.from(expectedSig)
);
if (!isValidSig) throw new Error('Tampered signature');
// 3. Parse and validate claims
const payload = JSON.parse(Buffer.from(payloadB64, 'base64url').toString('utf8'));
const now = Math.floor(Date.now() / 1000);
if (payload.exp && now >= payload.exp) {
throw new Error('Token has expired');
}
return payload;
}
Frequently Asked Questions
Are JWT tokens encrypted?
No. Standard JWTs (specifically JWS - JSON Web Signature) are signed, not encrypted. The header and payload are simply Base64URL-encoded text that anyone can read with our JWT Decoder. Never put passwords, API keys, or raw personal data inside a standard JWT. If confidentiality is required, you must use JWE (JSON Web Encryption).
How do you invalidate or revoke a JWT before it expires?
Because JWTs are stateless, immediate revocation requires maintaining a revocation list:
- Short Expiration Windows: Keep Access Tokens short-lived (e.g., 5–15 minutes) and issue new ones via Refresh Tokens.
- Token Blacklist / Allowlist in Redis: Store revoked
jti(JWT IDs) in an in-memory Redis cache with a TTL equal to the token's remaining lifespan. - User Password / Version Epoch: Include a
tokenVersionclaim in the payload. When a user logs out of all devices or changes passwords, increment their version in the database to instantly invalidate older tokens.
What is the difference between Access Tokens and Refresh Tokens?
An Access Token is a short-lived credential passed in the HTTP Authorization: Bearer <token> header to access protected API resources. A Refresh Token is a long-lived credential stored securely in an HttpOnly cookie, used exclusively with the auth server to obtain new access tokens without requiring re-authentication.
Interactive Tools for JSON Web Token (JWT)
Free, browser-based utilities to test, generate, and inspect JSON Web Token (JWT) payloads directly.
JWT Decoder
developer-toolsDecode, inspect, and validate JWT tokens with claim and signature analysis.
Hash Generator
security-cryptoGenerate and verify cryptographic hashes with multiple algorithms.
Password Generator
security-cryptoGenerate secure passwords, passphrases, and PINs with real-time strength analysis.