TL;DR: A JWT is a three-part token (header.payload.signature) used for authentication. The header says how it's signed, the payload carries user data (claims), and the signature proves it hasn't been tampered with. The payload is NOT encrypted — anyone can read it.
What's a JWT and Why Should You Care?
Imagine you walk into a VIP club. The bouncer checks your ID, stamps your hand, and says "you're good." For the rest of the night, you just flash your stamp to get past any door — nobody needs to call the bouncer again. That stamp? That's basically a JWT.
A JSON Web Token (JWT, pronounced "jot") is a compact string that a server gives you after you log in. It contains information about who you are (your "claims"), and it's cryptographically signed so nobody can fake it. Every time you make an API request, you send this token along, and the server verifies the stamp instead of checking your credentials again.
Here's what one looks like:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkFtaXQgQ2hhdmRhIiwiaWF0IjoxNzc0MjI0MDAwfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
See those two dots? They split the token into three parts: Header, Payload, and Signature. Each part is Base64URL-encoded (which is why it looks like random characters).
Part 1: The Header (The "How")
The header is a tiny JSON object that says what type of token this is and how it was signed:
{
"alg": "HS256",
"typ": "JWT"
}
alg is the signing algorithm. The most common ones:
HS256— Uses a shared secret key (like a password both sides know)RS256— Uses a public/private key pair (the server signs with the private key, anyone can verify with the public key)none— No signature at all (dangerous! Servers should always reject this)
Fun Fact: JWT is officially pronounced "jot" (rhymes with "dot"), not "J-W-T." But honestly, nobody will judge you either way. It's like the GIF/JIF debate but with less anger.
Part 2: The Payload (The "What")
This is the good stuff — the actual data about the user. These key-value pairs are called "claims":
{
"sub": "1234567890",
"name": "Amit Chavda",
"email": "amit@example.com",
"role": "admin",
"iat": 1774224000,
"exp": 1774227600
}
Some claims are standard (defined in the JWT spec):
sub(subject) — Who this token is about (usually a user ID)exp(expiration) — When the token expires (Unix timestamp)iat(issued at) — When the token was creatediss(issuer) — Who made this tokenaud(audience) — Who this token is for
You can also add any custom claims you need — role, permissions, org_id, whatever your app requires.
Critical: The payload is only encoded (Base64), NOT encrypted. Anyone who has the token can read every claim. Never put passwords, credit card numbers, or secret data in JWT claims. It's a postcard, not a sealed envelope.
Part 3: The Signature (The "Proof")
The signature is what makes the whole thing secure. It's computed by taking the encoded header and payload, combining them with a dot, and running them through the signing algorithm with a secret key:
HMACSHA256(
base64UrlEncode(header) + "." + base64UrlEncode(payload),
"my-super-secret-key"
)
When the server receives a JWT, it recomputes the signature using its own secret key. If the result matches the signature in the token, two things are proven: the token was created by someone who knows the secret, and nobody has tampered with the content.
How JWT Authentication Actually Works
Here's the flow in a typical web app:
- You log in — Send username/password to the server
- Server creates a JWT — Packs your user info into claims, signs it, sends it back
- You store it — In memory or an httpOnly cookie (NOT localStorage, please)
- You make API requests — Include the token in every request's Authorization header
- Server verifies — Checks the signature, reads the claims, lets you in (or doesn't)
// Sending the token
fetch("/api/profile", {
headers: {
"Authorization": "Bearer eyJhbGciOiJIUzI1NiIs..."
}
});
// Server-side verification (Node.js)
const jwt = require("jsonwebtoken");
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
// decoded = { sub: "user_123", role: "admin", ... }
} catch (err) {
res.status(401).json({ error: "Invalid token" });
}
Symmetric vs. Asymmetric: Pick Your Lock
Symmetric (HS256) — One Key for Everything
Same secret key signs and verifies. Simple, but every service that needs to verify tokens must have the secret.
const token = jwt.sign(payload, "shared-secret");
const decoded = jwt.verify(token, "shared-secret");
Asymmetric (RS256) — A Key Pair
Private key signs, public key verifies. The public key can be shared freely — it can only verify, not create tokens. This is the go-to for microservice architectures.
// Auth service signs with private key
const token = jwt.sign(payload, privateKey, { algorithm: "RS256" });
// Any service verifies with public key
const decoded = jwt.verify(token, publicKey, { algorithms: ["RS256"] });
Expiration and Refresh: Don't Let Tokens Live Forever
// Create a token that expires in 1 hour
const token = jwt.sign(
{ sub: "user_123", role: "admin" },
secret,
{ expiresIn: "1h" }
);
The common pattern: short-lived access tokens (15 min - 1 hour) paired with longer-lived refresh tokens (days/weeks). When the access token expires, the refresh token gets you a new one without having to log in again.
Why not just make tokens last forever? Because if a token is stolen, the attacker has access until it expires. Short expiration = smaller window of damage. Pair with refresh tokens for a good balance of security and user experience.
The 6 JWT Security Mistakes Everyone Should Avoid
- Storing JWTs in localStorage — Vulnerable to XSS attacks. Use httpOnly cookies instead.
- Not checking the algorithm — Always specify which algorithms your server accepts. Attackers love setting
"alg": "none". - Weak secrets — For HS256, use at least 256 bits of randomness. "mysecret" won't cut it.
- Sensitive data in the payload — Remember: encoded is not encrypted.
- No expiration — A JWT without
explives forever. Always set one. - No revocation plan — JWTs are stateless, so you can't "log out" a token. Use short expiration + a server-side blocklist for emergencies.
Try It Yourself
Paste a JWT into our decoder to instantly see its header, payload, and signature. Check expiration dates, inspect claims, and understand exactly what your tokens contain.
Open JWT Decoder →