What is JWT? How JSON Web Tokens Work Explained Simply
What Is a JWT?
A JSON Web Token (JWT, pronounced “jot”) is a compact, self-contained way to securely transmit information between two parties as a JSON object. JWTs are most commonly used for authentication — after a user logs in, the server creates a JWT and sends it to the client. The client includes the JWT in subsequent requests to prove its identity.
JWTs are everywhere in modern web development. If you have built or used an API with bearer token authentication, you have almost certainly worked with JWTs.
The Structure of a JWT
A JWT consists of three parts separated by dots:
xxxxx.yyyyy.zzzzz
Header.Payload.Signature
Here is a real (decoded) example:
Header
{
"alg": "HS256",
"typ": "JWT"
}
The header specifies the signing algorithm (HS256 = HMAC with SHA-256) and the token type.
Payload
{
"sub": "1234567890",
"name": "Jane Smith",
"email": "jane@example.com",
"role": "admin",
"iat": 1709251200,
"exp": 1709337600
}
The payload contains claims — statements about the user and metadata. Common claims include:
- sub (subject): The user identifier
- iat (issued at): When the token was created
- exp (expiration): When the token expires
- iss (issuer): Who created the token
- aud (audience): Who the token is intended for
- Custom claims: Any application-specific data (role, permissions, name)
Signature
HMACSHA256(
base64UrlEncode(header) + "." + base64UrlEncode(payload),
secret
)
The signature is created by hashing the encoded header and payload with a secret key. This is what makes the JWT tamper-proof — if anyone modifies the header or payload, the signature will not match, and the server will reject the token.
The Complete Token
Each part is Base64Url-encoded and concatenated with dots:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkphbmUgU21pdGgiLCJpYXQiOjE3MDkyNTEyMDB9.
SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
That long string of characters is a complete JWT that any server with the secret key can verify and decode.
How JWT Authentication Works
The Login Flow
- User submits credentials: Username and password sent to the server.
- Server verifies credentials: Checks against the database.
- Server creates a JWT: Encodes user information and signs it with a secret key.
- Server sends the JWT: Returns the token to the client.
- Client stores the JWT: Typically in memory, localStorage, or an HTTP-only cookie.
Subsequent Requests
- Client sends the JWT: Included in the
Authorizationheader:Authorization: Bearer eyJhbGciOiJIUzI1NiIs... - Server verifies the JWT: Checks the signature and expiration.
- Server reads the claims: Extracts user ID, role, and permissions from the payload.
- Server processes the request: No database lookup needed to identify the user.
Why This Is Powerful
With traditional session-based auth, the server must look up the session in a database for every request. With JWT, the server can verify and read the token using only the secret key — no database query needed. This makes JWTs ideal for:
- Stateless APIs: No server-side session storage required
- Microservices: Any service with the secret key can verify the token
- Scalability: Load-balanced servers do not need shared session storage
JWT vs Session-Based Auth
| Feature | JWT | Sessions |
|---|---|---|
| Server storage | None (stateless) | Session store required |
| Scalability | Excellent (any server can verify) | Requires shared session store |
| Revocation | Difficult | Easy (delete session) |
| Size | Larger (token carries data) | Small (just a session ID) |
| Cross-domain | Easy | Requires CORS configuration |
| Mobile friendly | Yes (simple header) | Cookies can be tricky |
Security Best Practices
Use HTTPS Always
JWTs are signed, not encrypted. Anyone who intercepts a JWT can read the payload (it is just Base64-encoded JSON). Always transmit JWTs over HTTPS to prevent interception.
Keep Payloads Small
Do not store sensitive data in the JWT payload. No passwords, credit card numbers, or secrets. The payload is readable by anyone with the token. Include only the minimum claims needed: user ID, role, and expiration.
Set Short Expiration Times
Short-lived tokens (15-60 minutes) limit the damage if a token is compromised. Use refresh tokens to get new access tokens without requiring the user to log in again.
Use Strong Secrets
For HMAC algorithms (HS256, HS384, HS512), use a cryptographically random secret at least 256 bits long. Generate one with the Password Generator — a 32+ character random string works well.
Prefer RS256 Over HS256 for Public APIs
- HS256 (symmetric): Same secret signs and verifies. Good for single-server applications.
- RS256 (asymmetric): Private key signs, public key verifies. Better for microservices and public APIs where you want services to verify tokens without having the signing key.
Store Tokens Securely
- Best: HTTP-only, secure, SameSite cookies (immune to XSS)
- Acceptable: In-memory (lost on page refresh, but safe from XSS)
- Risky: localStorage (accessible to any JavaScript on the page, vulnerable to XSS)
Validate Everything
On every request, the server should:
- Verify the signature
- Check that the token is not expired (
expclaim) - Verify the issuer (
iss) and audience (aud) if present - Validate that the algorithm matches what you expect (prevents algorithm confusion attacks)
Never Use alg: none
The JWT spec includes an “none” algorithm that produces unsigned tokens. Legitimate use cases are rare. Always reject tokens with alg: none unless you have a specific reason not to.
Common JWT Pitfalls
Not Handling Token Expiration
If your client does not check expiration and refresh tokens proactively, users get logged out with failed requests instead of smooth re-authentication.
Storing Too Much Data
Every claim increases the token size, and the token is sent with every request. A JWT with 20 custom claims bloats every API call. Keep it minimal.
No Revocation Strategy
JWTs cannot be invalidated by default (they are stateless). If you need to revoke access (user banned, password changed, suspicious activity), you need a strategy:
- Short expiration + refresh tokens: Tokens expire quickly; refuse to refresh revoked users
- Token blacklist: Maintain a list of revoked tokens (partially defeats the stateless advantage)
- Token versioning: Include a version number in the JWT; increment it on revocation
Algorithm Confusion Attacks
Some JWT libraries have been vulnerable to attacks where the attacker changes the algorithm in the header (e.g., from RS256 to HS256) and signs with the public key. Always explicitly specify the expected algorithm when verifying tokens.
Decoding JWTs
JWTs are not encrypted — they are signed. You can decode any JWT to see its contents:
// Decode the payload (no verification)
const payload = JSON.parse(atob(token.split('.')[1]));
console.log(payload);
This is useful for debugging, but remember: decoding is not verification. Always verify the signature on the server before trusting any claims.
Conclusion
JWT is a powerful, widely-adopted standard for API authentication. It enables stateless, scalable systems where any server can verify user identity without shared session storage. The key is using JWTs correctly: short expiration times, strong secrets, HTTPS-only transmission, and minimal payloads.
For generating strong secrets for JWT signing, use the Password Generator. To hash data for integrity checks alongside your JWT workflow, try the Hash Generator.