Tutorial2024-02-05

JWT Decoding Tutorial: How to Read and Debug JSON Web Tokens

Learn how to decode JWT tokens, understand their structure, debug authentication issues, and implement JWT security best practices in your applications.

#jwt#authentication#security#api#web-development

JSON Web Tokens (JWT) are the standard for secure authentication in modern web applications. From single sign-on (SSO) systems to REST API authorization, JWTs are everywhere in today's distributed architecture.

Understanding how to decode and debug JWT tokens is crucial for developers working with APIs and authentication systems. In this tutorial, you'll learn the complete anatomy of a JWT, how to decode tokens in multiple languages, common debugging workflows, security vulnerabilities to watch for, and best practices used by production systems.

What is a JWT?

A JWT is a compact, URL-safe token format that contains claims (data) as a JSON object. The token consists of three parts separated by dots:

Header.Payload.Signature

Example JWT:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQsW5c

JWT Structure

The header typically consists of two parts: the type of token (JWT) and the signing algorithm used.

{
  "alg": "HS256",
  "typ": "JWT"
}

This JSON is Base64Url encoded to form the first part of the token.

Payload

The payload contains the claims — statements about an entity (typically the user) and additional metadata.

{
  "sub": "1234567890",
  "name": "John Doe",
  "iat": 1516239022,
  "exp": 1516242822,
  "role": "admin"
}

Common claim types:

  • Registered claims: iss (issuer), exp (expiration), sub (subject), aud (audience)
  • Public claims: Custom claims registered with IANA
  • Private claims: Custom claims agreed between parties

Signature

The signature is created by taking the encoded header, encoded payload, a secret, and the algorithm specified in the header:

HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  secret)

How to Decode a JWT

Manual Decoding

Since JWT parts are just Base64Url encoded, you can decode them manually:

  1. Split the token by .
  2. Base64Url decode each part
  3. Parse the JSON

Note: JWT uses Base64Url encoding (not standard Base64) — it replaces + with - and / with _, and removes padding = characters.

Using JavaScript

function decodeJWT(token) {
  const parts = token.split('.');
  // Base64Url to Base64: replace - with + and _ with /
  const base64 = (str) =>
    str.replace(/-/g, '+').replace(/_/g, '/') +
    '==='.slice((str.length + 3) % 4);

  const header = JSON.parse(atob(base64(parts[0])));
  const payload = JSON.parse(atob(base64(parts[1])));

  return { header, payload, signature: parts[2] };
}

const decoded = decodeJWT('eyJhbGci...');
console.log(decoded.payload.name); // "John Doe"
console.log(decoded.payload.exp);  // 1516242822 (Unix timestamp)

Using Python

import base64
import json

def decode_jwt(token: str) -> dict:
    parts = token.split('.')
    # Add padding if needed
    def b64_decode(s):
        s += '=' * (4 - len(s) % 4)
        return base64.urlsafe_b64decode(s)

    header = json.loads(b64_decode(parts[0]))
    payload = json.loads(b64_decode(parts[1]))
    return {'header': header, 'payload': payload, 'signature': parts[2]}

decoded = decode_jwt('eyJhbGci...')
print(decoded['payload']['name'])  # "John Doe"

Using Node.js (with jsonwebtoken library)

const jwt = require('jsonwebtoken');

const token = 'eyJhbGci...';
const decoded = jwt.decode(token, { complete: true });

console.log(decoded.header);   // { alg: 'HS256', typ: 'JWT' }
console.log(decoded.payload);  // { sub: '1234567890', name: 'John Doe', ... }

Using Online Tools

For quick debugging, online JWT decoders let you paste your token and instantly see the decoded contents. This is invaluable for:

  • Debugging authentication errors in development
  • Verifying token expiration and issued-at timestamps
  • Inspecting custom claims and roles
  • Checking the signing algorithm used
  • Sharing decoded output with teammates during troubleshooting

JWT Security Best Practices

1. Always Validate on Server

Never trust JWT data on the client side. Always validate the signature and claims on your server. Decoding is not the same as verifying — anyone can read the payload, but only the server with the correct key can verify the signature.

// ❌ Wrong: just decoding without verification
const payload = jwt.decode(token);

// ✅ Right: verify signature + claims
const payload = jwt.verify(token, secretKey, {
  algorithms: ['HS256'],  // whitelist acceptable algorithms
  issuer: 'https://auth.example.com',
  audience: 'https://api.example.com'
});

2. Set Expiration Times

Always include exp claims to limit token validity:

{
  "exp": 1516242822,  // Short-lived access tokens
  "iat": 1516239022
}

3. Use HTTPS

Always transmit JWTs over HTTPS to prevent interception.

4. Store Securely

  • Browser: Use httpOnly cookies, not localStorage
  • Mobile: Use secure storage (Keychain/Keystore)

5. Implement Refresh Tokens

Use short-lived access tokens with longer-lived refresh tokens:

Access Token: 15 minutes
Refresh Token: 7 days

Common JWT Errors

"Token expired"

The exp claim has passed. Implement token refresh logic:

async function fetchWithAuth(url, options = {}) {
  let token = getAccessToken();

  // Check if token is about to expire (within 60 seconds)
  const payload = decodeJWT(token).payload;
  if (payload.exp * 1000 - Date.now() < 60000) {
    token = await refreshAccessToken();
  }

  return fetch(url, {
    ...options,
    headers: { ...options.headers, Authorization: `Bearer ${token}` }
  });
}

"Invalid signature"

The token was tampered with or the signing key is incorrect. Common causes:

  • Server restarted and lost its signing key (in-memory keys)
  • Multiple servers using different keys
  • Key rotation without updating all instances

"Malformed token"

The token structure is invalid — missing parts or incorrect encoding. Check for:

  • Truncated tokens (copy-paste errors)
  • Extra whitespace or newline characters
  • Tokens from a different environment (dev vs production)

JWT vs Session Cookies: When to Use Which

Feature JWT Session Cookie
State Stateless (no server storage) Stateful (server stores session)
Scalability Excellent (no lookup needed) Requires shared session store
Size Larger (contains data) Small (just session ID)
Revocation Difficult (can't invalidate before expiry) Easy (delete server session)
Best for APIs, microservices, mobile apps Traditional web apps

Modern recommendation: Use JWT for API communication and short-lived access tokens (15 min) combined with refresh tokens stored server-side for revocability.

Common JWT Attacks to Watch For

The "none" Algorithm Attack

Some libraries accept alg: "none", effectively disabling signature verification. Always whitelist acceptable algorithms on your server.

Key Confusion Attack

An attacker changes the algorithm from RS256 (asymmetric) to HS256 (symmetric) and signs the token using the public key as the HMAC secret. Mitigation: always specify expected algorithms server-side.

Token Leakage

JWTs in URLs (via Authorization header in redirects) can leak through browser history, logs, or referrer headers. Best practices:

  • Use POST requests with tokens in headers, never GET query params
  • Implement short expiration times
  • Use httpOnly cookies when possible

Conclusion

Understanding JWT structure and decoding is essential for modern web development. Whether you're debugging authentication issues, implementing secure APIs, or troubleshooting SSO integrations, knowing how to inspect and validate JWT tokens will make you a more effective developer.

Remember: decoding is free, verification costs a key. Always verify signatures server-side, use short-lived tokens, and never store sensitive data in the payload that could be read by anyone who intercepts the token.

Need to decode a JWT token? Try our free JWT Decoder — instantly decode and inspect JWT tokens in your browser, completely client-side with no data sent to any server.

🛠

Try It Yourself

Put what you've learned into practice with our free online tools.