A JSON Web Token (JWT) is a compact, self-contained string used to securely transmit information between two parties. JWTs are most commonly used for authentication — after a user logs in, the server issues a JWT, and the client includes it in subsequent requests to prove identity without the server needing to look up a session in a database.
The Structure of a JWT
A JWT consists of three parts separated by dots:
xxxxx.yyyyy.zzzzz
header.payload.signature
Each part is Base64URL-encoded (a URL-safe variant of Base64). The three parts are:
1. Header
The header declares the token type and the signing algorithm:
{
"alg": "HS256",
"typ": "JWT"
}
Common algorithms are HS256 (HMAC-SHA256, symmetric) and RS256 (RSA-SHA256, asymmetric).
2. Payload
The payload contains the claims — statements about the user and any additional metadata:
{
"sub": "1234567890",
"name": "Jane Smith",
"email": "jane@example.com",
"role": "admin",
"iat": 1710000000,
"exp": 1710086400
}
Standard claim names include:
sub— subject (usually a user ID)iat— issued at (Unix timestamp)exp— expiration (Unix timestamp)iss— issueraud— audience
3. Signature
The signature is computed by taking the encoded header and payload, joining them with a dot, and signing with a secret or private key:
HMACSHA256(
base64UrlEncode(header) + "." + base64UrlEncode(payload),
secret
)
The signature is what makes JWTs tamper-evident. If anyone modifies the header or payload, the signature will no longer match, and the server will reject the token.
JWTs Are Not Encrypted
This is the most important thing to understand about JWTs: the payload is encoded, not encrypted. Anyone who gets the token can decode and read the claims — all they need is a Base64URL decoder.
Never put sensitive data like passwords, credit card numbers, or secrets in a JWT payload. Only include what the receiving system needs to know.
How JWT Authentication Works
- The user submits their credentials (username + password)
- The server verifies them and issues a signed JWT
- The client stores the JWT (usually in memory or localStorage)
- On each subsequent request, the client sends the JWT in the
Authorizationheader:Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... - The server verifies the signature and reads the claims — no database lookup required
This stateless design is a key advantage over session-based authentication: any server instance can validate a JWT independently, which makes JWTs well-suited for distributed systems and APIs.
Access Tokens vs. Refresh Tokens
JWTs used for authentication typically come in two flavors:
Access tokens are short-lived (minutes to hours) and are sent with every API request. Their short lifespan limits the damage if one is stolen.
Refresh tokens are long-lived (days to weeks) and are only used to obtain new access tokens. They're kept in a more secure location (httpOnly cookies rather than JavaScript-accessible storage) and are never sent to API endpoints directly.
Common JWT Pitfalls
Using alg: "none": Some early JWT libraries accepted tokens with no signature if the algorithm was set to "none". Never accept tokens with this algorithm.
Not validating expiry: Always check the exp claim. A signature check alone doesn't tell you if the token is still valid.
Storing JWTs in localStorage: Accessible to any JavaScript on the page, making it vulnerable to XSS attacks. Prefer httpOnly cookies for sensitive applications.
Putting too much in the payload: JWTs travel with every request. A bloated payload adds unnecessary overhead. Keep claims minimal.
To decode a JWT and inspect its header, payload, and expiry, use the JWT Decoder.