JWT Structure
eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NSJ9.signature
ββββββββ Header βββββββββββββββ Payload ββββββββββ Signature βββ
Three Base64URL-encoded parts separated by dots.
{
"alg": "HS256",
"typ": "JWT"
}
| Field | Required | Values |
|---|
alg | Yes | HS256, HS384, HS512, RS256, RS384, RS512, ES256, ES384, ES512, PS256, EdDSA, none (reject this!) |
typ | No | Usually βJWTβ |
kid | No | Key ID β identifies which key to use for verification |
Registered Claims (RFC 7519)
| Claim | Name | Type | Example |
|---|
sub | Subject | string | "user-123" |
iss | Issuer | string | "https://auth.example.com" |
aud | Audience | string or array | "https://api.example.com" |
exp | Expiration | number (Unix timestamp) | 1716239022 |
iat | Issued At | number (Unix timestamp) | 1516239022 |
nbf | Not Before | number (Unix timestamp) | 1516239022 |
jti | JWT ID | string | "abc-123-def" |
All claims are optional. Custom claims (e.g., role, email, permissions) can be added freely.
Signing Algorithms
Symmetric (shared secret)
| Algorithm | Description | Key Size |
|---|
| HS256 | HMAC + SHA-256 | 256-bit secret |
| HS384 | HMAC + SHA-384 | 384-bit secret |
| HS512 | HMAC + SHA-512 | 512-bit secret |
Same key signs and verifies. Both parties must have the secret.
Asymmetric (public/private key pair)
| Algorithm | Description | Key Type |
|---|
| RS256 | RSA + SHA-256 | RSA 2048+ bit |
| RS384 | RSA + SHA-384 | RSA 2048+ bit |
| RS512 | RSA + SHA-512 | RSA 2048+ bit |
| ES256 | ECDSA + P-256 | EC P-256 |
| ES384 | ECDSA + P-384 | EC P-384 |
| ES512 | ECDSA + P-521 | EC P-521 |
| PS256 | RSASSA-PSS + SHA-256 | RSA 2048+ bit |
| EdDSA | Edwards-curve DSA | Ed25519 |
Private key signs, public key verifies. Better for distributed systems.
HS256 vs RS256
| HS256 | RS256 |
|---|
| Type | Symmetric | Asymmetric |
| Key | Shared secret | Public/private pair |
| Sign | Secret | Private key |
| Verify | Same secret | Public key |
| Speed | Faster | Slower |
| Best for | Single server | Microservices, 3rd-party verification |
Decode (Without Verification)
JavaScript
const [header, payload] = token.split('.').slice(0, 2)
.map(p => JSON.parse(atob(p.replace(/-/g, '+').replace(/_/g, '/'))));
Python
import jwt
payload = jwt.decode(token, options={"verify_signature": False})
Command Line
echo "eyJhbGci..." | cut -d. -f2 | base64 -d 2>/dev/null
Verify (With Key)
JavaScript (jsonwebtoken)
const jwt = require('jsonwebtoken');
const decoded = jwt.verify(token, secret, { algorithms: ['HS256'] });
Python (PyJWT)
import jwt
payload = jwt.decode(token, secret_key, algorithms=["HS256"])
Go (golang-jwt)
token, err := jwt.Parse(tokenString, func(t *jwt.Token) (interface{}, error) {
return []byte(secretKey), nil
})
Token Lifetime Pattern
Login β Access Token (15 min) + Refresh Token (7-30 days)
β
βββ API Request β send Access Token in Authorization header
βββ Access Token expires β use Refresh Token to get new Access Token
βββ Refresh Token expires β user must log in again
Security Checklist
Common Mistakes
| Mistake | Why Itβs Bad | Fix |
|---|
alg: none accepted | Attacker can forge tokens without signature | Whitelist algorithms: { algorithms: ['HS256'] } |
| Payload treated as secret | Anyone can Base64-decode the payload | Never put passwords/keys in JWT |
| Long expiration (24h+) | Stolen token is valid for too long | Use 15-60 min access + refresh token |
| Stored in localStorage | XSS can steal the token | Use httpOnly secure cookies |
| Secret key too short | Brute-force attack possible | Use 256+ bit random secret |
No exp validation | Expired tokens accepted | Always check expiration server-side |
Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NSJ9.signature
Useful Links