Back to Learn
JWTAuthenticationWeb Security

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.

November 18, 20248 min read

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 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 email or name.
  • Private claims — application-specific claims agreed between parties, like role or permissions.

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

ClaimNameDescription
subSubjectThe user or entity the token refers to (usually a user ID)
issIssuerThe server that created the token (e.g., auth.myapp.com)
audAudienceThe intended recipient of the token
expExpiryUnix timestamp after which the token must not be accepted
iatIssued AtUnix timestamp when the token was created
nbfNot BeforeUnix timestamp before which the token must not be accepted
jtiJWT IDUnique 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 (jsonwebtoken for Node, PyJWT for 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 exp claim. Libraries handle this automatically, but if you're parsing manually, always verify the token hasn't expired before trusting its claims.
  • Avoid the none algorithm. 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.