Learn/Practical Guides

JSON Web Tokens (JWT) — Decode, Verify & Debug

JWTs are the backbone of modern authentication. This guide breaks down how they work internally — the three-part structure, standard claims, signing algorithms, expiration handling, and the security mistakes that lead to real-world breaches.

What Is a JWT?

A JSON Web Token (JWT), defined in RFC 7519, is a compact string that carries a JSON payload and a cryptographic signature. It looks like this:

1eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

Three parts, separated by dots:

JWT Structure

Anatomy: Header, Payload, Signature

1. Header

The header is a JSON object that declares the token type and signing algorithm:

1{
2 "alg": "HS256",
3 "typ": "JWT"
4}

Common algorithms: HS256 (symmetric HMAC), RS256 (asymmetric RSA), ES256 (ECDSA). The header is Base64URL-encoded, not encrypted.

2. Payload (Claims)

The payload carries claims — key-value pairs with information about the user and token:

1{
2 "sub": "user_8742",
3 "name": "Alice Chen",
4 "email": "[email protected]",
5 "role": "admin",
6 "iat": 1711929600,
7 "exp": 1711933200
8}

Warning

The payload is only Base64URL-encoded, not encrypted. Anyone can decode it. Never store passwords, credit card numbers, or secrets in a JWT payload.

Registered Claims (RFC 7519)

ClaimFull NamePurpose
issIssuerWho created and signed the token
subSubjectWho the token represents (usually a user ID)
audAudienceIntended recipient (your API domain)
expExpirationUnix timestamp when the token becomes invalid
nbfNot BeforeToken is invalid before this timestamp
iatIssued AtWhen the token was created
jtiJWT IDUnique token identifier (for revocation)

3. Signature

The signature prevents tampering. For HS256:

1HMAC-SHA256(
2 base64UrlEncode(header) + "." + base64UrlEncode(payload),
3 secret
4)

If anyone changes a single character in the header or payload, the signature will not match and the token is rejected.

Decoding a JWT (No Secret Needed)

Because the header and payload are just Base64URL-encoded, you can decode them without knowing the secret. This is useful for debugging:

Decode JWT in JavaScriptjavascript
1function decodeJwt(token) {
2 const [headerB64, payloadB64] = token.split('.');
3 const header = JSON.parse(atob(headerB64.replace(/-/g, '+').replace(/_/g, '/')));
4 const payload = JSON.parse(atob(payloadB64.replace(/-/g, '+').replace(/_/g, '/')));
5 return { header, payload };
6}
7
8const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VyXzg3NDIiLCJyb2xlIjoiYWRtaW4iLCJleHAiOjE3MTE5MzMyMDB9.abc123';
9const { header, payload } = decodeJwt(token);
10
11console.log(header); // { alg: "HS256", typ: "JWT" }
12console.log(payload); // { sub: "user_8742", role: "admin", exp: 1711933200 }

Tip

Decoding reveals the claims but does not verify the signature. Always verify the signature server-side before trusting the claims.

Verifying a JWT

Verification means checking three things: (1) the signature is valid, (2) the token is not expired, and (3) the audience/issuer match your expectations.

Node.js — jsonwebtoken libraryjavascript
1import jwt from 'jsonwebtoken';
2
3const SECRET = process.env.JWT_SECRET;
4
5try {
6 const decoded = jwt.verify(token, SECRET, {
7 algorithms: ['HS256'],
8 audience: 'https://api.example.com',
9 issuer: 'https://auth.example.com',
10 });
11 console.log('Valid token:', decoded);
12} catch (err) {
13 if (err.name === 'TokenExpiredError') {
14 console.error('Token has expired at:', err.expiredAt);
15 } else if (err.name === 'JsonWebTokenError') {
16 console.error('Invalid token:', err.message);
17 }
18}
Python — PyJWT librarypython
1import jwt
2
3SECRET = "your-secret-key"
4
5try:
6 decoded = jwt.decode(
7 token,
8 SECRET,
9 algorithms=["HS256"],
10 audience="https://api.example.com",
11 )
12 print("Valid:", decoded)
13except jwt.ExpiredSignatureError:
14 print("Token has expired")
15except jwt.InvalidTokenError as e:
16 print(f"Invalid token: {e}")

The JWT Authentication Flow

JWT Auth Flow

Access Tokens vs Refresh Tokens

PropertyAccess TokenRefresh Token
LifetimeShort (5–15 minutes)Long (hours to days)
PurposeAuthorize API requestsGet new access tokens
Stored inMemory or HttpOnly cookieHttpOnly cookie (never localStorage)
Sent toResource APIAuth server only
ContainsUser claims, roles, permissionsMinimal: user ID + token ID
If stolenAttacker has short windowRotate immediately + invalidate

Important

Never send refresh tokens to resource APIs. The refresh token should only be sent to the authentication endpoint that issues new access tokens.

Signing Algorithms: HS256 vs RS256 vs ES256

AlgorithmTypeKeyBest For
HS256Symmetric (HMAC)Single shared secretSimple apps where signer and verifier are the same server
RS256Asymmetric (RSA)Private key signs, public key verifiesMicroservices: auth server signs, other services verify with public key
ES256Asymmetric (ECDSA)Smaller keys than RSAMobile apps, IoT — smaller token size and faster verification
Choosing an Algorithm

Common JWT Security Mistakes

1. Using 'none' Algorithm

Some libraries accept "alg": "none" in the header, which means no signature verification. An attacker can forge any payload.

Malicious headerjson
1{
2 "alg": "none",
3 "typ": "JWT"
4}

Fix: Always explicitly specify allowed algorithms when verifying: algorithms: ['HS256'].

2. Storing Secrets in the Payload

The payload is not encrypted. Anyone who intercepts the token can Base64-decode it. Never include passwords, API keys, or PII beyond what is strictly necessary.

3. Not Validating exp, aud, and iss

If you only verify the signature but ignore expiration, audience, and issuer, a token meant for a different service or a stolen expired token can be reused.

4. Storing JWTs in localStorage

localStorage is accessible to any JavaScript on the page. A single XSS vulnerability exposes the token. Use HttpOnly cookies with SameSite=Strict and Secure flags instead.

5. Extremely Long Expiration Times

A JWT valid for 30 days means a stolen token gives the attacker a 30-day window. Use short-lived access tokens (5–15 min) with refresh token rotation.

JWT Best Practices

  • Always verify the signature and validate exp, aud, iss claims
  • Explicitly set algorithms in your verification library — never allow "none"
  • Keep access tokens short-lived (5–15 minutes) and use refresh tokens for longer sessions
  • Store tokens in HttpOnly cookies, not localStorage
  • Use asymmetric algorithms (RS256/ES256) in distributed systems
  • Include a jti claim for token revocation via a server-side blocklist
  • Minimize payload size — only include claims the API needs for authorization
  • Rotate signing keys periodically and support kid (Key ID) in the header

Try It — Decode a JWT Payload

Paste a JWT payload (the middle section, between the two dots) into the editor. It should be valid JSON once decoded:

Try It Yourself

A valid JWT payload — experiment by adding custom claims

Frequently Asked Questions

What is a JSON Web Token (JWT)?
A JWT is a compact, URL-safe token format defined in RFC 7519. It encodes a JSON header and payload, then signs them so the recipient can verify authenticity without contacting the issuer. JWTs are widely used for authentication and authorization in web APIs.
Is the payload of a JWT encrypted?
No. A standard JWT (JWS) is only signed, not encrypted. The payload is Base64URL-encoded, which means anyone can decode and read it. Never put secrets like passwords in a JWT payload. If you need encryption, use JWE (JSON Web Encryption), which is a separate standard.
What happens when a JWT expires?
The "exp" claim is a Unix timestamp. When the current time exceeds this value, the token is expired. Most libraries reject expired tokens automatically. Users typically get a new token via a refresh token flow or by logging in again.
Should I store JWTs in localStorage or cookies?
HttpOnly cookies are generally safer because JavaScript cannot access them, which prevents XSS attacks from stealing the token. localStorage is simpler but vulnerable to XSS. For sensitive apps, prefer HttpOnly cookies with SameSite and Secure flags.
Can a JWT be revoked?
JWTs are stateless — once issued, they are valid until expiration. To "revoke" a JWT, you need a server-side deny list (blocklist) that checks token IDs. Alternatively, use short expiration times with refresh tokens so compromised tokens become useless quickly.
What is the difference between HS256 and RS256?
HS256 (HMAC-SHA256) uses a single shared secret for both signing and verifying. RS256 (RSA-SHA256) uses a private key to sign and a public key to verify. RS256 is preferred in distributed systems where multiple services need to verify tokens without sharing a secret.