What Is JWT? A Complete Guide to JSON Web Tokens
Every time you log into a REST API, call a microservice, or authenticate a mobile app, there is a good chance a JSON Web Token (JWT) is involved. JWTs have become the dominant mechanism for stateless authentication across web services, OAuth 2.0 flows, and OpenID Connect. This guide explains what JWT is, how its three-part structure works, which algorithms to use, and how to avoid the most common security mistakes.
What Is JWT?
A JSON Web Token (JWT, pronounced “jot”) is a compact, URL-safe token format defined by RFC 7519. It encodes a JSON object as a cryptographically signed string that can be verified without a database lookup.
A JWT looks like three base64url-encoded strings joined by dots:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VyXzEyMyIsIm5hbWUiOiJBbGljZSIsInJvbGVzIjpbImFkbWluIl0sImlhdCI6MTcxNjQxNjQwMCwiZXhwIjoxNzE2NDIwMDAwfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5cThe three parts are the header, payload, and signature — separated by dots. Together they form a self-contained token: anyone who holds the signing key can verify the token is authentic and has not been tampered with, without any network call.
JWTs are used primarily for authentication (proving who you are) and authorization (proving what you can access). They are the token format of choice for REST APIs, OAuth 2.0 access tokens, OpenID Connect ID tokens, and mobile app authentication.
A Brief History of JWT
JWT emerged from the IETF's JOSE (JSON Object Signing and Encryption) working group, which began standardising JSON-based cryptographic primitives around 2011. Before JWT, web applications used opaque session tokens, SAML assertions (XML-based), or proprietary formats — none of which worked well for stateless, cross-origin REST APIs.
Key milestones:
- 2011–2013 — JOSE working group drafts circulate. JSON Web Signature (JWS), JSON Web Encryption (JWE), JSON Web Key (JWK), and JSON Web Algorithms (JWA) are developed alongside JWT.
- February 2014 — OpenID Connect 1.0 is published, using JWT as its ID token format. This drove rapid adoption across identity providers (Google, Microsoft, Okta).
- May 2015 — RFC 7519 (JWT), RFC 7515 (JWS), RFC 7516 (JWE), RFC 7517 (JWK), and RFC 7518 (JWA) are published as official IETF standards.
- 2015–present — JWT becomes the default token format for OAuth 2.0 access tokens, adopted by Auth0, AWS Cognito, Firebase Auth, and every major API gateway.
The key innovation JWT brought was statelessness: the token itself carries all the information needed to authenticate a request, so servers do not need to share session state or hit a database on every API call.
JWT Structure
A JWT has three parts, each independently base64url-encoded and joined by dots: header.payload.signature.
Header
The header is a JSON object that describes the token type and the signing algorithm used. It is base64url-encoded to form the first segment.
{
"alg": "HS256",
"typ": "JWT"
}The alg field tells the verifier which algorithm was used to sign the token. HS256 means HMAC with SHA-256. The typ field is always "JWT".
Payload
The payload is a JSON object containing claims — statements about the user and metadata about the token itself. It is base64url-encoded to form the second segment.
{
"sub": "user_123",
"name": "Alice Johnson",
"email": "alice@example.com",
"roles": ["admin", "editor"],
"iss": "https://auth.example.com",
"aud": "https://api.example.com",
"iat": 1716416400,
"exp": 1716420000
}Important: the payload is encoded, not encrypted. Anyone who holds the token can decode and read it. Never store passwords, payment card numbers, or other secrets in a JWT payload unless you are using JWE encryption.
Signature
The signature is computed over the encoded header and payload using the algorithm specified in the header:
HMACSHA256(
base64url(header) + "." + base64url(payload),
secret
)The signature is what gives JWT its security guarantee: any modification to the header or payload produces a different signature, and the server will reject the token. Only a party that holds the signing secret (or private key, for asymmetric algorithms) can produce a valid signature.
Decode Any JWT Instantly
Paste a JWT into the free JWT Decoder to inspect its header, payload, and expiry — decoded in your browser, never sent to a server.
Open JWT DecoderStandard Claims
JWT claims are the key-value pairs in the payload. RFC 7519 defines three categories: registered, public, and private claims.
Registered Claims
These are standardised, short-named claims with well-known meanings. They are not mandatory but strongly recommended:
| Claim | Name | Description |
|---|---|---|
| iss | Issuer | The server or service that issued the token |
| sub | Subject | The user or entity the token is about (e.g. user ID) |
| aud | Audience | Intended recipient — the API or service that should accept the token |
| exp | Expiration | Unix timestamp after which the token is invalid |
| nbf | Not Before | Unix timestamp before which the token must not be accepted |
| iat | Issued At | Unix timestamp when the token was created |
| jti | JWT ID | Unique identifier for this token — used for denylist revocation |
Public and Private Claims
Public claims are registered in the IANA JSON Web Token Claims Registry and must have collision-resistant names (typically URIs). Private claims are custom fields agreed upon between the token producer and consumer — for example, roles, email, or org_id.
Keep the payload small. Every API request carries the token, so large payloads increase bandwidth. Include only claims that the receiver actually needs.
Format Your JWT Payload
Paste the decoded payload JSON into the JSON Formatter to pretty-print, validate, and explore the structure of any JWT claims.
Open JSON FormatterSigning Algorithms
The alg field in the JWT header determines how the signature is computed. RFC 7518 (JSON Web Algorithms) defines two main families: symmetric HMAC algorithms and asymmetric public-key algorithms.
| Algorithm | Type | Key | Use case |
|---|---|---|---|
| HS256 | HMAC-SHA256 | Shared secret | Single-server apps, simple APIs |
| HS384 / HS512 | HMAC-SHA384/512 | Shared secret | Higher-security HMAC variants |
| RS256 | RSA-SHA256 | Public/private key pair | Multi-service architectures, OIDC |
| ES256 | ECDSA-P256 | Elliptic curve key pair | High performance, mobile SDKs |
| none | Unsigned | — | Never use in production |
HS256 — Symmetric (HMAC)
HMAC algorithms use a shared secret: the same key signs and verifies the token. This is simple and fast, but requires every service that validates tokens to know the secret. If the secret is leaked, all tokens can be forged. Use HS256 for single-service applications.
RS256 — Asymmetric (RSA)
RSA algorithms use a key pair: the private key signs the token, and the public key verifies it. This is ideal for multi-service architectures: the auth server holds the private key, and all other services only need the public key (which can be published safely). RS256 is the standard for OIDC providers.
ES256 — Asymmetric (ECDSA)
ECDSA uses elliptic curve cryptography to achieve the same security as RSA with significantly shorter key sizes and faster computation. ES256 is popular in mobile and IoT contexts where bandwidth and CPU are constrained.
The “none” Algorithm — Never Use
Setting alg to "none" produces an unsigned JWT with no signature. Some libraries that trusted the token's own alg header were vulnerable to attacks where an adversary replaced a valid algorithm with "none" and forged arbitrary claims. Always whitelist allowed algorithms in your verification call.
How JWT Authentication Works
The standard JWT authentication flow follows six steps:
- User logs in — the client sends credentials (username + password, OAuth code, etc.) to the authentication server.
- Server signs a JWT — on successful authentication, the server creates a JWT containing the user's identity and permissions, signs it with the secret key, and returns it.
- Client stores the token — the client stores the access token (in memory or an HttpOnly cookie) and the refresh token (in an HttpOnly cookie).
- Client sends the token — on every subsequent API request, the client includes the JWT in the
Authorization: Bearer <token>header. - Server verifies the signature — the API server verifies the JWT's signature, checks the
exp,iss, andaudclaims. No database lookup required. - Access granted — if verification passes, the server reads the claims from the payload to determine what the user is allowed to do.
The power of this flow is statelessness: the server does not store sessions. Any server instance that holds the verification key can validate any JWT, which makes horizontal scaling trivial and eliminates shared session storage entirely.
The trade-off is revocation difficulty: once issued, a JWT is valid until it expires. This is why access tokens should be short-lived (5–15 minutes) paired with longer-lived, revocable refresh tokens.
JWT vs Session Tokens
JWTs are not always the right choice. Traditional session cookies suit many applications better. Here is a detailed comparison:
| Feature | JWT | Session Cookie |
|---|---|---|
| Storage | Client-side (localStorage / memory / cookie) | Server-side session store |
| Scalability | Stateless — no shared storage needed | Requires shared store (Redis, DB) |
| Revocation | Difficult — must wait for exp or use denylist | Easy — delete the session record |
| Token size | ~200–500 bytes | Small (session ID only) |
| Self-contained | Yes — payload carries all claims | No — server looks up session data |
| Validation cost | CPU (signature verification) | Network/DB round-trip |
| CSRF risk | Lower with Authorization: Bearer header | Higher with cookies (without SameSite) |
| Best for | REST APIs, microservices, mobile apps | Traditional web apps with SSR |
Verdict: use JWTs for REST APIs, microservices, and mobile apps where stateless, cross-origin authentication is needed. Use session cookies for traditional server-rendered web apps where instant revocation and smaller tokens are more important.
Generating JWTs
Always use a well-maintained library — implementing JWT cryptography from scratch is error-prone and a common source of security vulnerabilities.
Node.js — jsonwebtoken
import jwt from 'jsonwebtoken'; // npm install jsonwebtoken
// Sign an access token (15-minute expiry)
const token = jwt.sign(
{
sub: 'user_123',
name: 'Alice',
roles: ['admin'],
},
process.env.JWT_SECRET,
{
expiresIn: '15m',
issuer: 'https://auth.example.com',
audience: 'https://api.example.com',
}
);
// Verify and decode (throws if invalid or expired)
const payload = jwt.verify(token, process.env.JWT_SECRET, {
issuer: 'https://auth.example.com',
audience: 'https://api.example.com',
algorithms: ['HS256'], // always whitelist
});
console.log(payload.sub); // 'user_123'Python — PyJWT
import jwt # pip install PyJWT
from datetime import datetime, timedelta, timezone
SECRET = 'your-secret-key'
# Create access token
payload = {
'sub': 'user_123',
'name': 'Alice',
'iss': 'https://auth.example.com',
'aud': 'https://api.example.com',
'iat': datetime.now(timezone.utc),
'exp': datetime.now(timezone.utc) + timedelta(minutes=15),
}
token = jwt.encode(payload, SECRET, algorithm='HS256')
# Verify and decode
decoded = jwt.decode(
token,
SECRET,
algorithms=['HS256'], # always whitelist
audience='https://api.example.com',
issuer='https://auth.example.com',
)
print(decoded['sub']) # 'user_123'Library Quick Reference
| Language | Library | Sign | Verify |
|---|---|---|---|
| Node.js | jsonwebtoken | jwt.sign(payload, secret, opts) | jwt.verify(token, secret, opts) |
| Python | PyJWT | jwt.encode(payload, secret, alg) | jwt.decode(token, secret, algs) |
| Go | golang-jwt/jwt | NewWithClaims().SignedString(key) | ParseWithClaims(token, &claims, fn) |
| Ruby | ruby-jwt | JWT.encode(payload, secret, alg) | JWT.decode(token, secret, true, opts) |
| Java | java-jwt (Auth0) | JWT.create().sign(algorithm) | JWT.require(alg).build().verify(token) |
Verify Your Generated Token
Paste your generated JWT into the JWT Decoder to confirm the header, payload, and expiry are exactly what you expect before deploying.
Open JWT DecoderVerifying JWTs
Token verification is the most security-critical step. A correct verification flow must:
- Check the signature using the correct key. Reject immediately if it does not match.
- Validate
exp— reject tokens that have expired. - Validate
nbf(if present) — reject tokens that are not yet valid. - Validate
iss— confirm the token came from the expected issuer. - Validate
aud— confirm the token is intended for this service. - Whitelist the algorithm — never accept
alg: "none"or unexpected algorithms.
Good JWT libraries handle steps 1–5 automatically when you pass the expected issuer and audience in the verify options. Step 6 requires you to explicitly specify the allowed algorithms — this is the one step developers most often omit.
For asymmetric algorithms (RS256, ES256), the public key can be fetched from the issuer's JWKS (JSON Web Key Set) endpoint — a URL such as https://auth.example.com/.well-known/jwks.json. Libraries like jose (Node.js) or python-jose can fetch and cache JWKS automatically.
Security Best Practices
- ✓
1. Always validate exp, iss, and aud
These three claims are your primary defences against token replay attacks and cross-service token abuse. Pass issuer and audience to your verify call — never rely on defaults.
- ✓
2. Use RS256 or ES256 for multi-service architectures
Asymmetric algorithms mean only the auth server needs the private key. All other services verify with the public key, which can be shared freely. If any service is compromised, it cannot forge new tokens.
- ✓
3. Never skip signature verification
jwt.decode() (without verify) is for debugging only. In production, always use jwt.verify(). Parsing an unverified token and trusting its claims is a critical vulnerability.
- ✓
4. Store tokens securely
Store access tokens in memory (JavaScript variable), not localStorage, to reduce XSS risk. Store refresh tokens in HttpOnly, Secure, SameSite=Strict cookies, which are inaccessible to JavaScript.
- ✓
5. Keep access tokens short-lived; use refresh tokens
A 15-minute access token limits the damage from a stolen token. Pair it with a longer-lived refresh token (stored in an HttpOnly cookie) that your auth server can revoke by deleting the record from a database.
Common Errors
jwt expired
Problem
// Token exp: 1716420000
// Current time: 1716430000
// → TokenExpiredError: jwt expiredFix
// Keep access tokens short-lived (15 min)
// Implement a refresh token flow
const token = jwt.sign(payload, secret, { expiresIn: '15m' })The exp claim is a Unix timestamp. When the current time exceeds it, the token is invalid. Implement refresh tokens so users are not forced to re-login.
invalid signature
Problem
// Server signs with SECRET_A
// Client verifies with SECRET_B
// → JsonWebTokenError: invalid signatureFix
// Ensure the same key is used for signing and verification
// Check environment variables on all services
const payload = jwt.verify(token, process.env.JWT_SECRET)The most common cause is a mismatch between the secret used to sign and the secret used to verify. Check that environment variables are consistent across all services.
jwt malformed
Problem
// Token is truncated or the "Bearer " prefix was not stripped
const token = req.headers.authorization; // "Bearer eyJ..."Fix
// Strip the "Bearer " prefix before passing to jwt.verify
const token = req.headers.authorization?.split(' ')[1];
const payload = jwt.verify(token, secret);A JWT must be exactly three base64url segments separated by dots. Truncation, extra whitespace, or the "Bearer " prefix left in will cause this error.
alg: "none" accepted
Problem
// Attacker crafts a token with alg: "none"
// Server omits algorithm whitelist
const payload = jwt.decode(token); // no verification!Fix
// Always whitelist allowed algorithms in verify options
const payload = jwt.verify(token, secret, {
algorithms: ['HS256'], // explicit whitelist
});The "none" algorithm attack lets an attacker forge tokens by setting alg to "none" and omitting the signature. Always specify an algorithm whitelist in your verify call.
Frequently Asked Questions
- What does JWT stand for?
- JWT stands for JSON Web Token. It is an open standard (RFC 7519) that defines a compact, self-contained way to transmit information between parties as a digitally signed JSON object. The information can be verified and trusted because it is cryptographically signed.
- Is JWT encrypted?
- No — a standard JWT (JWS, JSON Web Signature) is base64url-encoded but not encrypted. The header and payload are readable by anyone who decodes the token. Sensitive data should never be stored in a JWT unless you use JWE (JSON Web Encryption, RFC 7516). The signature prevents tampering but not inspection.
- What is the difference between JWT and OAuth?
- OAuth 2.0 is an authorization framework that defines how applications can obtain limited access to user accounts. JWT is a token format. OAuth can use JWTs as access tokens, but it can also use opaque tokens. They are complementary: OAuth provides the protocol flow, JWT provides the token structure.
- How do I decode a JWT online?
- Paste any JWT into the free JWT Decoder at jsonbuddy.net/jwt-decoder. It splits the token at the dots, base64url-decodes each part, and displays the header, payload, and expiry in a human-readable format — entirely in your browser, never sent to a server.
- Can JWT tokens be revoked before they expire?
- Not natively — JWTs are stateless, so the server has no record to delete. Common strategies include: keeping access token TTL very short (5–15 minutes), maintaining a server-side token blocklist keyed by the jti claim, or using short-lived access tokens paired with revocable refresh tokens stored in a database.
Decode & Debug JWTs Instantly
Inspect any JWT token's header, payload, and expiry in your browser. Fully client-side — your token never leaves your device.