JWT Tokens Explained: Structure, Signing, and Security Best Practices
JSON Web Tokens (JWTs) are widely used for authentication and authorisation. Learn how JWTs are structured, how signing works, the difference between HS256 and RS256, and the critical security rules every developer must follow.
What Is a JWT?
A JSON Web Token (JWT) is a compact, URL-safe token format defined in RFC 7519. JWTs are commonly used to represent authentication claims between a client and a server.
A JWT looks like this:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkFsaWNlIiwiaWF0IjoxNjAwMDAwMDAwfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
It is three Base64URL-encoded segments separated by dots: Header.Payload.Signature.
The Three Parts
Header
{
"alg": "HS256",
"typ": "JWT"
}
Specifies the signing algorithm and token type.
Payload (Claims)
{
"sub": "1234567890",
"name": "Alice",
"email": "alice@example.com",
"role": "admin",
"iat": 1600000000,
"exp": 1600086400
}
The payload contains claims — statements about the user or other data. Standard claims:
| Claim | Name | Description |
|---|---|---|
iss | Issuer | Who issued the token |
sub | Subject | Who the token is about (usually user ID) |
aud | Audience | Who the token is intended for |
exp | Expiration | Unix timestamp when the token expires |
iat | Issued At | Unix timestamp when the token was issued |
jti | JWT ID | Unique token identifier (for revocation) |
Signature
The signature verifies that the token was issued by a trusted party and has not been modified:
HMAC_SHA256(
base64url(header) + "." + base64url(payload),
secret_key
)
Signing Algorithms
HS256 (HMAC-SHA256)
Uses a single shared secret key. Both the issuer (server) and the verifier must know the same secret. Suitable for monolithic applications where the same service issues and verifies tokens.
Risk: Any party that can verify JWTs can also issue them. Do not use HS256 in multi-service architectures unless all services are equally trusted.
RS256 (RSA-SHA256)
Uses an RSA key pair: the server signs with the private key, and clients/services verify with the public key. The public key can be distributed openly (e.g., via a JWKS endpoint).
Preferred for:
- Microservices — each service only has the public key
- Third-party verification — clients can verify tokens without server round-trips
- Auth servers (e.g., Auth0, Cognito, Google)
ES256 (ECDSA-SHA256)
Like RS256 but uses shorter elliptic curve keys for equivalent security. Smaller tokens, faster signature generation.
Critical Security Rules
1. Always verify the signature
Never trust a JWT's claims without first verifying the signature. Base64-decoding the payload without verifying gives you untrustworthy data.
2. Validate exp (expiration)
if (payload.exp < Math.floor(Date.now() / 1000)) {
throw new Error("Token expired");
}
3. Validate aud and iss
An attacker could take a valid JWT from one service and replay it to another. Verify that the token was meant for your service:
if (payload.aud !== "https://api.myservice.com") throw new Error("Invalid audience");
if (payload.iss !== "https://auth.myservice.com") throw new Error("Invalid issuer");
4. Never use alg: "none"
Some early JWT libraries accepted "alg": "none" (no signature) as valid. This completely bypasses security. Libraries must reject alg: none by default.
5. Use short expiration times
Access tokens should expire in minutes (15–60 min), not hours or days. Use refresh tokens (rotated on use) for long-lived sessions.
JWTs Are NOT Encrypted by Default
A standard JWT (JWS — JSON Web Signature) is signed but not encrypted. Anyone who intercepts the token can read the payload by Base64-decoding it. Never include sensitive data (passwords, secrets, PII) in a JWT payload unless using JWE (JSON Web Encryption).
Where to Store JWTs in the Browser
| Storage | XSS risk | CSRF risk | Notes |
|---|---|---|---|
localStorage | High | Low | Accessible to any JS on the page |
sessionStorage | High | Low | Cleared on tab close |
HttpOnly cookie | None | Moderate | Cannot be read by JS; CSRF mitigated with SameSite |
Best practice: Store JWTs in HttpOnly, Secure, SameSite=Strict cookies. This prevents XSS from stealing the token. Pair with CSRF tokens if using SameSite=None.