·6 min read

JWT Tokens Explained: What's Inside Your Auth Token?

JWT tokens are in every modern web application's Authorization header — but most developers just copy-paste the pattern without understanding what's inside. Here's a complete breakdown.

Ram profile

Ram

JWT token structure showing header payload and signature
Share

Every time you log into a modern web application, your browser stores a JWT token. Every subsequent API request sends it. But what exactly is inside that token? What makes it secure? When should you use JWT vs session cookies?

Decode any JWT instantly with our free JWT Decoder — no signing secret needed to inspect the header and payload.

What Is a JWT?

A JSON Web Token (JWT) is a compact, URL-safe token format for securely transmitting claims between parties as a JSON object. It is defined in RFC 7519.

A JWT is self-contained: it carries all the information needed to identify a user and their permissions within the token itself. The server doesn't need to look up a session database for every request — it just verifies the token's signature.

JWT Structure: Three Parts

Every JWT has three Base64URL-encoded parts separated by dots:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IlJhbSIsImlhdCI6MTUxNjIzOTAyMn0.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

Format: HEADER.PAYLOAD.SIGNATURE

Part 1: Header

The header identifies the algorithm used to sign the token:

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

Common algorithms:

  • HS256 (HMAC SHA-256): Symmetric signing — same secret used to sign and verify
  • RS256 (RSA SHA-256): Asymmetric — private key signs, public key verifies
  • ES256 (ECDSA): Elliptic curve — smaller keys, same security as RS256
Base64URL-encode this JSON and you get the first part: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9

Part 2: Payload (Claims)

The payload contains the claims — statements about the user:

{
  "sub": "user_12345",
  "name": "Ram Charan",
  "email": "ram@example.com",
  "role": "admin",
  "iat": 1746000000,
  "exp": 1746003600
}
Registered Claims (standard):
ClaimMeaning
subSubject — typically user ID
iatIssued At — Unix timestamp of creation
expExpiration — Unix timestamp after which token is invalid
nbfNot Before — token invalid before this timestamp
issIssuer — who created the token
audAudience — who the token is intended for
jtiJWT ID — unique identifier for this token
Custom Claims (your app-specific data): Anything you add beyond the registered claims. User roles, permissions, plan type, team ID — whatever your application needs to make authorization decisions without a database lookup.

The payload is Base64URL-encoded (not encrypted). Anyone can decode it — including with our JWT Decoder. Never put sensitive data (passwords, private keys, SSNs) in a JWT payload.

Part 3: Signature

The signature is what makes JWTs secure:

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

For HS256, the server takes the encoded header and payload, concatenates them with a dot, then runs HMAC-SHA256 with the server's secret key.

When the server receives a JWT:

  1. Separate header, payload, signature
  2. Re-compute signature using the same secret key
  3. Compare with the received signature
  4. If they match, the token is authentic and unmodified
Why this works: You can decode the header and payload (they're just Base64URL), but you can't forge a valid signature without the secret key. Any modification to the payload — changing your role from "user" to "admin" — invalidates the signature, and the server rejects the token.

Decoding vs Verifying: An Important Distinction

Decoding: Base64URL decode the header and payload. Anyone can do this — no secret needed. Our JWT Decoder does exactly this. Verifying: Check the signature using the secret/public key. Only the server (or anyone with the key) can verify authenticity.

Decoding tells you what's in the token. Verifying confirms the token is genuine and unmodified.

JWT vs Session Cookies

FactorJWTSession Cookie
Server storageNone (stateless)Session store required
ScalabilityExcellent (no DB lookup)Requires session sharing across servers
Token invalidationDifficult (wait for expiry)Easy (delete session from store)
Payload sizeCan be large (in header every request)Small (just session ID)
SecuritySecure if implemented correctlySecure if HTTPS + httpOnly + SameSite
Best forMicroservices, APIs, mobileTraditional server-rendered apps
### When to Use JWT
  • Mobile apps where cookies are inconvenient
  • Microservices where multiple services need to verify identity
  • Cross-domain authentication (your API on api.example.com, frontend on app.example.com)
  • Stateless APIs at scale

When to Use Session Cookies

  • Traditional server-rendered web apps (Rails, Django, Laravel)
  • When you need immediate token revocation (logout invalidates immediately)
  • When payload size matters (cookies are small, JWT payload can grow large)

Common JWT Security Mistakes

1. Storing JWT in localStorage

LocalStorage is accessible to any JavaScript on the page, making JWTs stored there vulnerable to XSS attacks. Store JWTs in httpOnly cookies to prevent JavaScript access.

2. Not Validating the exp Claim

Many implementations decode the JWT and check the payload but forget to verify the token hasn't expired. Always validate exp server-side.

3. Using alg: none

Some JWT libraries allow alg: none which disables signature verification entirely. Explicitly reject tokens with alg: none in your server-side validation.

4. Weak Secrets for HS256

HS256 with a short or guessable secret is easily brute-forced. Use a randomly generated secret of at least 256 bits. For production, prefer RS256 or ES256 with proper key management.

5. Long Expiry Times

Long-lived JWTs (days or weeks) are dangerous because they can't be invalidated if compromised. Use short expiry (15 minutes) for access tokens and longer expiry for refresh tokens, with a token refresh flow.

Implementing JWT in Node.js

import jwt from 'jsonwebtoken';

const SECRET = process.env.JWT_SECRET;

// Generate token on login function generateToken(user) { return jwt.sign( { sub: user.id, email: user.email, role: user.role }, SECRET, { expiresIn: '15m' } ); }

// Verify token middleware function verifyToken(req, res, next) { const token = req.headers.authorization?.split(' ')[1]; if (!token) return res.status(401).json({ error: 'No token' }); try { const payload = jwt.verify(token, SECRET); req.user = payload; next(); } catch (err) { return res.status(401).json({ error: 'Invalid token' }); } }

Debugging JWT Issues

When your API returns 401:

  1. Paste your token into our JWT Decoder
  2. Check exp — is the token expired? Timestamps are in UTC
  3. Check iss and aud — does your server validate these claims?
  4. Check the alg in the header — does your server accept this algorithm?
  5. Verify the Authorization header format: Bearer <token> (note the space)
JWT tokens are now in virtually every modern application. Understanding their structure, security model, and failure modes makes debugging authentication issues dramatically faster — and writing secure auth code more reliable.
Share

Related Articles

Stay Updated

Get the latest articles delivered straight to your inbox. No spam, ever.