How to Read a JWT Token
JSON Web Tokens are everywhere in modern web authentication — but most developers use them without understanding what's inside. This guide decodes the mystery.
What is a JWT?
A JSON Web Token (JWT) is a compact, URL-safe way to represent claims between two parties. In practice, you encounter them as opaque-looking strings in HTTP headers:
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
That jumble of characters is not random — it has a precise, documented structure. Once you know how to read it, JWTs become transparent.
The three parts
Every JWT consists of exactly three sections, separated by dots (.):
HEADER.PAYLOAD.SIGNATURE
Each section is Base64URL-encoded — a URL-safe variant of Base64 that replaces + with - and / with _. This is not encryption. Anyone can decode these sections instantly.
The header
The first section is the header. It identifies the token type and the signing algorithm used:
// Base64URL: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
// Decoded:
{
"alg": "HS256",
"typ": "JWT"
}alg— the signing algorithm. Common values:HS256(HMAC-SHA256),RS256(RSA-SHA256),ES256(ECDSA).typ— always"JWT"for standard tokens.
⚠ Security note
Never set alg to "none" on the server side. This disables signature verification entirely and has been exploited in many real-world attacks.
The payload
The second section is the payload, containing the actual data (called claims). This is where user identity, roles, and expiry information live:
// Base64URL: eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ
// Decoded:
{
"sub": "1234567890",
"name": "John Doe",
"iat": 1516239022
}The payload can contain any JSON object. There are three categories of claims:
- Registered claims — standardized claims with short names:
sub,iss,exp,iat,nbf,aud,jti. - Public claims — custom claims registered with IANA, like
emailorname. - Private claims — application-specific claims agreed between parties, like
roleorpermissions.
The signature
The third section is the signature. It's computed by the server that issued the token:
HMACSHA256( base64url(header) + "." + base64url(payload), secret_key )
The signature proves two things: (1) the token came from a trusted issuer who knows the secret key, and (2) the header and payload haven't been modified since issuance. If even one character changes, the signature becomes invalid.
Crucially, the signature only verifies integrity and authenticity — it does not encrypt the payload. Anyone who intercepts a JWT can read the header and payload. Never put sensitive data (passwords, credit cards, SSNs) in a JWT payload.
Standard claims — what each field means
| Claim | Name | Description |
|---|---|---|
sub | Subject | The user or entity the token refers to (usually a user ID) |
iss | Issuer | The server that created the token (e.g., auth.myapp.com) |
aud | Audience | The intended recipient of the token |
exp | Expiry | Unix timestamp after which the token must not be accepted |
iat | Issued At | Unix timestamp when the token was created |
nbf | Not Before | Unix timestamp before which the token must not be accepted |
jti | JWT ID | Unique identifier to prevent replay attacks |
Decoding a JWT manually
You can decode any JWT yourself in seconds. Take the first section (before the first dot) and Base64URL-decode it. In JavaScript:
const token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.dozjgNryP4J3jVmNHl0w5N_XgL0n3I9PlFUP0THsR8U";
const [headerB64, payloadB64] = token.split('.');
// Decode header
const header = JSON.parse(atob(headerB64.replace(/-/g, '+').replace(/_/g, '/')));
console.log(header); // { alg: "HS256", typ: "JWT" }
// Decode payload
const payload = JSON.parse(atob(payloadB64.replace(/-/g, '+').replace(/_/g, '/')));
console.log(payload); // { sub: "1234567890" }Security gotchas every developer should know
- JWTs are not sessions. A signed JWT is valid until it expires — you can't easily revoke one mid-lifetime. If a token is stolen, it remains valid. Design your expiry strategy carefully (short-lived access tokens + long-lived refresh tokens is the standard pattern).
- Always verify the signature server-side. Never trust a client-provided JWT without cryptographic verification. Use a well-maintained library (
jsonwebtokenfor Node,PyJWTfor Python) — don't roll your own. - Don't store sensitive data in the payload. The payload is Base64-encoded, not encrypted. It is readable by anyone. Use JWE (JSON Web Encryption) if you need confidential payload data.
- Check the
expclaim. Libraries handle this automatically, but if you're parsing manually, always verify the token hasn't expired before trusting its claims. - Avoid the
nonealgorithm. Some libraries historically accepted"alg": "none"to bypass signature verification. Always explicitly specify allowed algorithms in your verification code.
Try decoding a JWT
The fastest way to internalize this is to decode a real token. Our JWT Decoder splits any token into its three parts, pretty-prints the JSON, decodes all timestamp claims to human-readable dates, and tells you if the token is expired — all in your browser, with no data sent to any server.