JWT Tokens Explained: How to Read and Debug JSON Web Tokens (2026)

javascript dev.to

JSON Web Tokens (JWTs) power authentication in almost every modern web application — from small startups to enterprise systems. Yet many developers use JWTs daily without fully understanding how they work, how to debug them, or how to secure them properly. This guide covers everything you need to know about JWT tokens in 2026.

What is a JWT Token?

A JSON Web Token (JWT, pronounced "jot") is a compact, URL-safe token format used to represent claims securely between two parties. Defined in RFC 7519, JWTs have become the dominant mechanism for authentication and authorization in modern web applications and APIs.

Unlike opaque session tokens that require a server-side lookup, JWTs are self-contained: they carry all necessary information within the token itself, encoded as a JSON object. This stateless property makes JWTs particularly well-suited for distributed systems and microservice architectures.

A typical JWT looks like this:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkFsaWNlIiwiaWF0IjoxNzA5MjUxMjAwfQ.dGhpc19pc19hX2Zha2Vfc2lnbmF0dXJl
Enter fullscreen mode Exit fullscreen mode

That seemingly random string is actually three distinct parts separated by dots. Let's break it down.

JWT Structure: Header, Payload, and Signature

Every JWT consists of three parts separated by dots:

xxxxx.yyyyy.zzzzz
 |      |      |
header  payload  signature
Enter fullscreen mode Exit fullscreen mode

Each part is Base64URL-encoded independently, then concatenated with dots.

1. Header

The header describes the token type and signing algorithm:

{"alg":"HS256","typ":"JWT"}
Enter fullscreen mode Exit fullscreen mode
  • alg — the algorithm (HS256, RS256, ES256, etc.)
  • typ — always "JWT"
  • kid — (optional) key identifier for key rotation

2. Payload

The payload contains claims — statements about the user and metadata:

{"sub":"1234567890","name":"Alice","email":"alice@example.com","role":"admin","iat":1709251200,"exp":1709254800}
Enter fullscreen mode Exit fullscreen mode

Claims come in three categories:

  • Registered claims — Standard names like iss (issuer), exp (expiration), sub (subject), aud (audience)
  • Public claims — Application-defined, should use collision-resistant names
  • Private claims — Custom fields agreed between issuer and consumer

3. Signature

The signature is created by signing the encoded header and payload with a secret key:

HMACSHA256(
  base64UrlEncode(header) + "." + base64UrlEncode(payload),
  secret
)
Enter fullscreen mode Exit fullscreen mode

This ensures the token hasn't been tampered with. Changing a single character invalidates the signature.

⚠️ Important: JWTs are encoded, not encrypted. Anyone can decode and read a JWT. The security comes from the signature proving authenticity — not from hiding the contents.

How JWT Authentication Works

Here's the typical JWT authentication flow:

1. User submits credentials (username/password)
   ↓
2. Server verifies credentials
   ↓
3. Server creates JWT with user claims
   ↓
4. Server signs JWT with secret key
   ↓
5. Client stores JWT (memory or HTTP-only cookie)
   ↓
6. Client sends JWT in Authorization header on every request
   ↓
7. Server verifies signature and claims
   ↓
8. Server processes request with user info from claims
Enter fullscreen mode Exit fullscreen mode

Example Request

GET /api/user/profile HTTP/1.1
Host: api.example.com
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWI...
Enter fullscreen mode Exit fullscreen mode

Server Verification Steps

For each request, the server performs three verification checks:

  1. Decode the header and payload to read the claims
  2. Verify the signature using the same algorithm and key
  3. Check the token hasn't expired (exp claim vs current time)

Only if all three checks pass does the server process the request.

Common JWT Claims

Claim Name Purpose
iss Issuer Who issued the token
sub Subject Who the token is about (user ID)
aud Audience Who should accept the token
exp Expiration When the token becomes invalid
iat Issued At When the token was created
nbf Not Before Earliest time token is valid
jti JWT ID Unique token identifier

Custom claims can be anything you need: roles, permissions, tenant IDs, feature flags, etc. Just keep the payload small — the token is transmitted with every request.

How to Decode and Debug JWTs

Decoding a JWT is simple because the header and payload are just Base64URL-encoded.

JavaScript Example

function decodeJWT(token) {
  const [headerB64, payloadB64] = token.split('.');
  const header = JSON.parse(atob(headerB64));
  const payload = JSON.parse(atob(payloadB64));
  return { header, payload };
}

const token = "eyJhbGciOi...";
const decoded = decodeJWT(token);
console.log(decoded.payload);
// { sub: "1234567890", name: "Alice", exp: 1709254800 }
Enter fullscreen mode Exit fullscreen mode

Python Example

import base64
import json

def decode_jwt(token):
    header_b64, payload_b64, _ = token.split('.')
    # Add padding if needed
    payload_b64 += '=' * (4 - len(payload_b64) % 4)
    payload = json.loads(base64.urlsafe_b64decode(payload_b64))
    return payload

print(decode_jwt("eyJhbGciOi..."))
Enter fullscreen mode Exit fullscreen mode

Online Tools

For quick debugging, I built a free Base64 Decoder that can decode JWT payloads:

👉 StringToolsApp Base64 Decoder

Split the JWT at the dots, paste each part into the decoder, and instantly see the JSON contents.

JWT Security Best Practices

✅ DO

  1. Use strong asymmetric algorithms (RS256, ES256) for production
  2. Set short expiration times on access tokens (15 min to 1 hour)
  3. Implement refresh tokens for longer sessions
  4. Validate all claims: signature, exp, iss, aud
  5. Use HTTPS only — never send JWTs over HTTP
  6. Store access tokens in memory (not localStorage)
  7. Store refresh tokens in HTTP-only cookies
  8. Rotate refresh tokens on each use
  9. Use a whitelist of accepted algorithms during verification
  10. Log and monitor failed verification attempts

❌ DON'T

  1. Don't store sensitive data in JWT payloads (passwords, credit cards, SSNs)
  2. Don't use the none algorithm — it's a known attack vector
  3. Don't store JWTs in localStorage — vulnerable to XSS attacks
  4. Don't skip expiration checks — expired tokens must be rejected
  5. Don't trust the alg header blindly — whitelist allowed algorithms
  6. Don't use long-lived access tokens — limits damage from token theft
  7. Don't forget audience validation — prevents token misuse across services
  8. Don't overload the payload — keep it focused and small

Common JWT Mistakes to Avoid

Mistake #1: Storing JWTs in localStorage

localStorage is accessible to any JavaScript on your page. An XSS vulnerability gives attackers full access to stored tokens.

Better approach: Store access tokens in memory (React state, closure variables) and refresh tokens in HTTP-only cookies.

Mistake #2: No Token Revocation Strategy

JWTs remain valid until they expire. If a user changes their password or gets banned, outstanding JWTs can still be used.

Solutions:

  • Short expiration times (15 min)
  • Token blacklist/denylist
  • Version counter on user records
  • Refresh token rotation

Mistake #3: Not Validating Audience

In a microservice architecture, a token issued for the profile service shouldn't be accepted by the payment service.

Always set and validate the aud claim.

Mistake #4: Overloading the Payload

Large tokens are transmitted with every request. Oversized tokens:

  • Increase bandwidth usage
  • Slow down request processing
  • May exceed HTTP header limits (typically 8KB)

Keep payloads focused on authentication and authorization only. Fetch detailed user data separately.

Mistake #5: Using the none Algorithm

Some JWT libraries accept tokens with "alg": "none", allowing attackers to create unsigned tokens that pass validation.

Always whitelist accepted algorithms in your verification code.

Debugging JWT Issues

When JWT authentication fails, check these in order:

  1. Decode the token — Verify the claims look correct
  2. Check expiration — Is exp in the future?
  3. Check audience — Does aud match your service?
  4. Check issuer — Is iss what you expect?
  5. Verify signature — Is the signing key correct?
  6. Check algorithm — Does alg match your verification config?
  7. Clock skew — Are server clocks synchronized?
  8. Key rotation — Is the kid pointing to a valid key?

Conclusion

JWT tokens are a powerful authentication mechanism when used correctly. They enable stateless authentication, scale elegantly across microservices, and integrate seamlessly with modern web frameworks.

But they're not a silver bullet. JWTs require careful implementation, especially around storage, revocation, and validation. Follow the security best practices, validate all claims rigorously, and always remember: JWTs are encoded, not encrypted.


Try It Yourself 🚀

Need to decode a JWT quickly? I built a free Base64 Decoder that handles JWT payloads — no signup, 100% client-side:

👉 StringToolsApp Base64 Decoder

Split any JWT at the dots, paste the parts, and see the contents instantly.


What's your biggest JWT challenge? Token storage? Revocation? Refresh token rotation? Drop a comment below! 💬

Source: dev.to

arrow_back Back to Tutorials