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
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
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"}
-
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}
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
)
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
Example Request
GET /api/user/profile HTTP/1.1
Host: api.example.com
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWI...
Server Verification Steps
For each request, the server performs three verification checks:
- Decode the header and payload to read the claims
- Verify the signature using the same algorithm and key
-
Check the token hasn't expired (
expclaim 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 }
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..."))
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
- Use strong asymmetric algorithms (RS256, ES256) for production
- Set short expiration times on access tokens (15 min to 1 hour)
- Implement refresh tokens for longer sessions
- Validate all claims: signature, exp, iss, aud
- Use HTTPS only — never send JWTs over HTTP
- Store access tokens in memory (not localStorage)
- Store refresh tokens in HTTP-only cookies
- Rotate refresh tokens on each use
- Use a whitelist of accepted algorithms during verification
- Log and monitor failed verification attempts
❌ DON'T
- Don't store sensitive data in JWT payloads (passwords, credit cards, SSNs)
-
Don't use the
nonealgorithm — it's a known attack vector - Don't store JWTs in localStorage — vulnerable to XSS attacks
- Don't skip expiration checks — expired tokens must be rejected
-
Don't trust the
algheader blindly — whitelist allowed algorithms - Don't use long-lived access tokens — limits damage from token theft
- Don't forget audience validation — prevents token misuse across services
- 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:
- Decode the token — Verify the claims look correct
-
Check expiration — Is
expin the future? -
Check audience — Does
audmatch your service? -
Check issuer — Is
isswhat you expect? - Verify signature — Is the signing key correct?
-
Check algorithm — Does
algmatch your verification config? - Clock skew — Are server clocks synchronized?
-
Key rotation — Is the
kidpointing 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! 💬