A JSON Web Token (JWT) is a compact, URL-safe token used to securely transmit information between parties. It is most commonly used for authentication — once a user logs in, the server issues a JWT that the client sends with every subsequent request. The format is standardised in RFC 7519 (May 2015) and built on top of JWS (RFC 7515) and JWA (RFC 7518).
What are the three parts of a JWT?
A JWT is three Base64URL-encoded parts separated by dots:
header.payload.signatureHeader
Specifies the token type (JWT) and the signing algorithm (e.g. HS256, RS256).
{ "alg": "HS256", "typ": "JWT" }Payload
Contains claims — statements about the user and metadata. Standard claims include sub (subject), iat (issued at), and exp (expiry).
{ "sub": "user_123", "role": "admin", "exp": 1735689600 }Signature
Created by signing the encoded header and payload with a secret (symmetric) or private key (asymmetric). This prevents tampering.
HMACSHA256(base64url(header) + "." + base64url(payload), secret)How does JWT authentication work?
- User sends credentials to the server
- Server validates and returns a signed JWT
- Client stores the JWT (memory, localStorage, or httpOnly cookie)
- Client sends the JWT in the
Authorization: Bearer <token>header - Server verifies the signature on every request — no session lookup needed
What are the standard JWT claims?
RFC 7519 reserves seven claim names. The full list is maintained in the IANA JWT claim registry. Verifiers should validate iss, aud, and exp on every request.
| Claim | Name | Purpose |
|---|---|---|
| iss | Issuer | Who minted the token (e.g. "https://auth.example.com") |
| sub | Subject | Who the token is about — usually the user ID |
| aud | Audience | Who the token is for — reject if your service is not in this list |
| exp | Expiry | Unix seconds; reject after this time (must validate) |
| nbf | Not before | Unix seconds; reject before this time |
| iat | Issued at | Unix seconds when the token was minted |
| jti | JWT ID | Unique token identifier — used for replay protection and blocklists |
Which JWT signing algorithm should you use?
| Algorithm | Type | Use case |
|---|---|---|
| HS256 | Symmetric (HMAC) | Single-service auth, shared secret |
| RS256 | Asymmetric (RSA) | Multi-service, public key verification |
| ES256 | Asymmetric (ECDSA) | Compact keys, mobile/IoT |
Performance matters at scale: HS256 verification is ~30 µs; RS256 is ~1.2 ms — roughly 40× slower because RSA is asymmetric. ES256 sits in the middle at ~200 µs and produces 64-byte signatures vs RSA-2048's 256 bytes. Pick HS256 when one service mints and verifies; pick RS256 or ES256 when verifiers must not hold the signing key (multi-service, public APIs, OIDC).
How do you verify a JWT in code?
Always verify the signature, the algorithm, the issuer, and the audience. The jose library (Node, browsers, Workers, Deno, Bun) is the safest choice in JavaScript:
import { jwtVerify } from 'jose';
const secret = new TextEncoder().encode(process.env.JWT_SECRET);
const { payload } = await jwtVerify(token, secret, {
issuer: 'https://auth.example.com',
audience: 'api.example.com',
algorithms: ['HS256'],
});
console.log(payload.sub);In Python, use PyJWT:
import jwt
payload = jwt.decode(
token,
secret,
algorithms=['HS256'],
issuer='https://auth.example.com',
audience='api.example.com',
)
print(payload['sub'])What is the access token vs refresh token pattern?
Because JWTs are stateless, you cannot revoke one before it expires — so issue them with short lifetimes and pair them with a long-lived refresh token:
- Access JWT — short-lived (15 minutes is typical), sent on every API request in the
Authorizationheader. - Refresh token — long-lived (7–30 days), opaque random string stored server-side, kept in an
HttpOnly; Secure; SameSite=Strictcookie. The client posts it to/auth/refreshto mint a new access JWT. - Refresh tokens live in your database so you can revoke them on logout, password change, or suspected compromise. Access JWTs stay stateless.
What are the most common JWT pitfalls?
- Do not store sensitive data in the payload. The payload is only Base64URL-encoded, not encrypted — anyone can decode it.
- Always verify the signature server-side. Never trust a JWT that hasn't been verified.
- Set an expiry (
exp). Without it, a stolen token is valid forever. - Beware of the
alg: noneattack. Libraries that accept unsigned tokens are vulnerable (CVE-2015-9235, CVE-2016-10555). Always pin the expected algorithm — passalgorithms: ['HS256']or similar to your verifier. - Revocation is hard. JWTs are stateless — you can't invalidate one without a blocklist. Use short expiries and refresh tokens for sensitive apps.
See also: JWT best practices for the full RFC 8725 checklist (key entropy, audience binding, explicit typing, replay protection).
References
- RFC 7519 — JSON Web Token (JWT)
- RFC 7515 — JSON Web Signature (JWS)
- RFC 7518 — JSON Web Algorithms (JWA)
- RFC 8725 — JWT Best Current Practices
- IANA JSON Web Token Claims Registry
- jose — JavaScript JOSE/JWT library
Try the tools: JWT Debugger to inspect and decode tokens, or the JWT Generator to mint signed tokens for testing.