ToolMelt

What Is a JWT (JSON Web Token)?

How a JSON Web Token works: header, payload and signature segments, base64url encoding, the standard claims, and why decoding a JWT is not verifying it.

This guide pairs with a free tool:

Open the JWT Decoder

A JSON Web Token (JWT, RFC 7519) is a compact, URL-safe string that carries claims — statements about a user or session — between two parties, most often to prove that someone is logged in. Its anatomy fits on one line: header.payload.signature, three base64url-encoded segments joined by dots.

The three segments: header, payload, signature

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

SegmentContainsDecoded value
eyJhbGci... — headerToken metadata: signing algorithm and type.{"alg":"HS256","typ":"JWT"}
eyJzdWIi... — payloadThe claims — the data the token asserts.{"sub":"1234567890","name":"John Doe","iat":1516239022}
SflKxwRJ... — signatureHMAC-SHA256 of the first two segments, made by the issuer.Raw bytes, not JSON.

Header and payload are ordinary JSON once decoded. The signature is different: a cryptographic function over the exact string header.payload, so changing one character invalidates it — that is what makes a JWT tamper-evident, provided the verifier checks the signature.

Base64url is encoding, not encryption

Each segment uses base64url, the URL-safe Base64 variant from RFC 4648. Plain Base64 maps binary data onto A-Z, a-z, 0-9, plus + and /, padded with = — awkward characters in URLs and HTTP headers. Base64url swaps + for -, / for _, and omits padding: the bytes 0xFB 0xFF encode as +/8= in standard Base64 and as -_8 in base64url.

It is an encoding, not a cipher: anyone can reverse it with no key, so never put secrets such as passwords or API keys into a token.

The standard claims

RFC 7519 registers seven claim names. All are optional, but exp, iss and aud appear in almost every well-behaved token. Timestamps use NumericDate: seconds since the Unix epoch, not milliseconds.

ClaimStands forMeaningExample
issIssuerWho created and signed the token, usually a URL."https://auth.example.com"
subSubjectWho the token is about, typically a user ID."user-42"
audAudienceWho the token is for; a string or an array of strings. A server must reject a token whose aud does not name it."billing-api"
expExpiration timeThe token must not be accepted at or after this time.1757003600
iatIssued atWhen the token was created; used to compute its age.1757000000
nbfNot beforeThe token must not be accepted before this time.1757000000
jtiJWT IDA unique token identifier, used to detect replays or drive a revocation list."d8f7c2a1"

In these examples exp is exactly 3600 seconds after iat: a one-hour token. The iat 1516239022 in the sample token is 2018-01-18 01:30:22 UTC. A 13-digit value like 1516239022000 is milliseconds; read as seconds it lands tens of thousands of years in the future.

Decoding is not verifying

Reading a JWT requires no secret, so decoding says nothing about whether the token is genuine. Two classic attacks exploit verifiers that forget this:

Verifying properly means: check the signature with the right key, confirm the algorithm is the one you expect, then validate exp, nbf, iss and aud. Until all of that has happened on your server, treat decoded claims as untrusted input.

Safe debugging practices

Try it: paste any JWT into the JWT Decoder — it runs entirely in your browser, so the token never leaves your device. The tool pretty-prints the header and payload, renders exp, iat and nbf as human-readable UTC times with an Expired or Valid badge, and flags that the signature is not verified. No token at hand? Click Sample token to generate one with fresh timestamps.

More guides