If you've ever logged into a web app and seen a long string of characters split by dots, there's a good chance it was a JWT. JSON Web Tokens are the backbone of modern authentication — but they're often misunderstood, misused, and over-complicated.
Here's a clear breakdown of what a JWT is, what those three parts actually contain, and how to use them correctly.
What is a JWT?
A JWT (JSON Web Token) is a compact, URL-safe string that carries a JSON payload. It's most commonly used for authentication and authorization — a server issues a JWT when you log in, and your browser sends it with every subsequent request to prove who you are.
A JWT looks like this:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VyXzEyMyIsImVtYWlsIjoidGVzdEBleGFtcGxlLmNvbSIsImV4cCI6MTcyMDAwMDAwMH0.dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk
Three parts, separated by dots. Each part is Base64URL-encoded.
The three parts of a JWT
1. Header
The first part is the header. Decoded, it's a small JSON object describing the token type and the signing algorithm:
{
"alg": "HS256",
"typ": "JWT"
}
alg tells the receiver how the signature was created. Common values:
HS256— HMAC with SHA-256 (symmetric, shared secret)RS256— RSA with SHA-256 (asymmetric, public/private key pair)ES256— ECDSA with SHA-256 (asymmetric, more compact signatures)
2. Payload
The second part is the payload — the actual data the token carries. It's also a JSON object:
{
"sub": "user_123",
"email": "test@example.com",
"role": "admin",
"iat": 1720000000,
"exp": 1720003600
}
The keys inside are called claims. Some are standardized:
sub— subject (usually a user ID)iat— issued at (Unix timestamp)exp— expires at (Unix timestamp)iss— issuer (which server created the token)aud— audience (which server should accept the token)
You can add any custom claims you want. But remember: the payload is only Base64URL-encoded, not encrypted. Anyone who has the token can decode and read it. Never put passwords or sensitive data in the payload.
3. Signature
The third part is the signature. The server creates it by:
- Taking
base64url(header) + "." + base64url(payload) - Running it through the algorithm specified in the header, using a secret key
- Base64URL-encoding the result
HMACSHA256(
base64url(header) + "." + base64url(payload),
secretKey
)
The signature is what makes the token trustworthy. If anyone modifies the payload — even a single character — the signature won't match when the server verifies it. The server rejects the token.
How JWT authentication works
- Login: User sends username and password to the server.
- Issue: Server verifies credentials, creates a JWT signed with its secret key, and returns it.
- Store: Client stores the JWT (typically in memory or localStorage).
- Request: On each API call, the client sends the JWT in the
Authorizationheader:Authorization: Bearer eyJhbGci... - Verify: Server checks the signature. If valid and not expired, the request proceeds.
The server doesn't need to store any session data. The token is self-contained — it carries the user's identity and the signature proves it hasn't been tampered with. This is why JWTs scale well in distributed systems.
JWT vs session cookies
| JWT | Session cookies | |
|---|---|---|
| State | Stateless — no server storage | Stateful — session stored on server |
| Scalability | Scales easily across servers | Requires shared session storage |
| Revocation | Hard — must use a blocklist or short expiry | Easy — delete session on server |
| Size | Larger (200–800 bytes) | Small (session ID only) |
| Best for | APIs, microservices, mobile | Traditional web apps |
Neither is universally better. Session cookies are simpler and easier to revoke. JWTs are better for stateless APIs and mobile clients.
Common JWT mistakes
Storing JWTs in localStorage. JWTs in localStorage are accessible to JavaScript, which means any XSS vulnerability can steal them. For web apps, prefer httpOnly cookies. For SPAs and mobile apps, the tradeoffs are more nuanced.
Accepting alg: none. Some early JWT libraries allowed tokens with no signature if the header said "alg": "none". An attacker could craft arbitrary tokens. Always explicitly validate the algorithm on the server side and reject none.
Long expiry times. A JWT cannot be revoked without extra infrastructure. If you issue a token that lasts 30 days and a user's account is compromised, that token is valid for 30 days. Use short-lived access tokens (15 minutes to 1 hour) combined with refresh tokens.
Putting sensitive data in the payload. The payload is only encoded, not encrypted. Decode any JWT at toolko.io/tools/jwt-decoder and you'll see the plaintext JSON immediately. If you need encrypted tokens, use JWE (JSON Web Encryption) instead.
Decode a JWT in code
JavaScript:
// Decode payload (NOT signature verification — for inspection only)
function decodeJwt(token) {
const [, payload] = token.split('.');
return JSON.parse(atob(payload.replace(/-/g, '+').replace(/_/g, '/')));
}
// Verify (Node.js, using jsonwebtoken package)
const jwt = require('jsonwebtoken');
const decoded = jwt.verify(token, process.env.JWT_SECRET);
Python:
import jwt # pip install PyJWT
# Verify and decode
decoded = jwt.decode(token, secret, algorithms=["HS256"])
print(decoded) # {'sub': 'user_123', 'exp': 1720003600, ...}
Go:
import "github.com/golang-jwt/jwt/v5"
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
return []byte(secretKey), nil
})
claims, _ := token.Claims.(jwt.MapClaims)
Command line (decode without verification):
echo "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VyXzEyMyJ9.xxx" \
| cut -d. -f2 \
| base64 -d 2>/dev/null
Decode a JWT online
To quickly inspect what's inside a JWT — useful when debugging an API or understanding what claims a server issues — use Toolko's JWT Decoder. Paste the token and it shows you the decoded header, payload, and expiry time in readable form.
The tool runs entirely in your browser. Your token is never sent to a server.
FAQ
Q: Is the JWT payload encrypted? No. It's Base64URL-encoded, which is trivially reversible. Anyone who holds the token can read the payload. Only the signature requires the secret key to verify. If you need encrypted payload, use JWE.
Q: Can I use the same JWT library to create and verify tokens?
Yes — libraries like jsonwebtoken (Node.js), PyJWT (Python), and golang-jwt handle both signing and verification. Always verify on the server using a trusted secret or public key.
Q: My JWT is expired. What should I do?
Redirect the user to log in again, or use a refresh token to obtain a new access token. An expired JWT should always be rejected — never extend an expired token by modifying the exp claim.
Q: What's the difference between a JWT and an API key? An API key is a simple opaque string — the server looks it up in a database to find out what it means. A JWT is self-contained — the server can verify it and extract claims without any database lookup. JWTs are better for short-lived tokens in distributed systems; API keys are better for long-lived integrations where you want easy revocation.
Q: How long should a JWT be valid? Access tokens: 15 minutes to 1 hour. Pair with a refresh token (valid 7–30 days, stored securely) to maintain sessions without compromising security.