If you've ever inspected a login API response, you've probably seen a long string starting with eyJhbGci.... This guide walks through how a JWT (JSON Web Token) is structured, why it is not "encrypted", and what signature verification actually guarantees.
A JWT is three parts joined by dots
A JWT looks like header.payload.signature — three segments separated by two periods. Each segment is a JSON object encoded with Base64URL. Base64URL is almost identical to standard Base64, except it's safe to use directly inside a URL: + and / are replaced with - and _, and trailing = padding is dropped. For example, given a token like:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0IiwibmFtZSI6ImRldmVsb3BrIiwiZXhwIjoxNzUyMDAwMDAwfQ.4f3c2a1e...
decoding the first segment gives {"alg":"HS256","typ":"JWT"}, and the second gives {"sub":"1234","name":"developk","exp":1752000000}. The third segment isn't human-readable JSON — it's the raw bytes produced by signing the first two segments with a key.
What goes in the Header and Payload
The Header usually holds just two fields: alg, the signing algorithm (e.g. HS256, RS256), and typ, the token type (almost always JWT). The Payload carries "claims" — some are standardized, others are custom fields a service adds. Common standard claims include:
sub— subject, the user identifier the token representsiat— issued at, a Unix timestamp for when the token was createdexp— expiration, when the token stops being validnbf— not before, the token isn't valid until this time
On top of those, services freely add custom claims like role or email as needed.
Why it isn't encryption — anyone can decode Base64
This is the most common misconception. Base64URL encoding is just a way to represent data as safe text — it isn't a way to hide it. No key is required to reverse it; anyone can decode the payload back into readable JSON instantly. That means you should never put passwords, card numbers, or session secrets into a JWT payload — anyone holding the token (or intercepting it on the wire) can read the contents.
So what does a JWT actually guarantee? Integrity. The signature proves the header and payload haven't been tampered with since they were issued. In other words, a JWT isn't designed to hide its contents — it's designed to make the contents impossible to alter without detection.
How HS256 signature verification works
HS256 (HMAC-SHA256) is a symmetric scheme where the server (or two cooperating servers) share the same secret key. When issuing a token, the server signs the string base64url(header) + "." + base64url(payload) with that secret using HMAC-SHA256 and appends the result as the third segment. To verify, the receiver re-signs the received header and payload the same way and checks whether the result exactly matches the signature attached to the token. Without knowing the secret, an attacker can't modify the payload and produce a matching valid signature — so a matching signature means the token was issued by someone holding the secret and hasn't been altered since. Asymmetric algorithms like RS256 work on the same principle, just with separate keys for signing and verifying.
Common pitfalls
A few mistakes show up repeatedly in real-world implementations:
- Putting passwords or API keys in the payload — anyone can decode it
- Trusting payload data without verifying the signature — a client could tamper with it undetected
- Accepting
alg: none— a well-known vulnerability that skips signature checks entirely - Issuing tokens with no
exp— a stolen token then never expires
Want to check a real token's header and payload, or verify an HS256 signature with your own secret? You can do that instantly with our free tool.
Standard registered claims (RFC 7519)
The standard claims used in a JWT payload. All are optional, but most libraries automatically validate exp and nbf.
| Claim | Name | Meaning |
|---|---|---|
| iss | Issuer | Who issued the token |
| sub | Subject | The token's subject (usually a user ID) |
| aud | Audience | Intended recipient of the token |
| exp | Expiration Time | Expiry time (Unix timestamp, seconds) |
| nbf | Not Before | Invalid before this time |
| iat | Issued At | Time the token was issued |
| jti | JWT ID | Unique token identifier (prevents reuse) |
Common signing algorithms (alg header)
| alg | Method | Key type |
|---|---|---|
| HS256 | HMAC + SHA-256 | Symmetric (shared secret) |
| HS384 / HS512 | HMAC + SHA-384/512 | Symmetric |
| RS256 | RSA + SHA-256 | Asymmetric (private/public key) |
| ES256 | ECDSA + SHA-256 | Asymmetric (elliptic curve) |
| none | No signature | — (never allow) |
⚠️ alg: none omits the signature entirely; if a server accepts it, anyone can forge tokens. Always pin an allow-list of accepted algorithms during verification.
🪪 Try it yourself with the tool
Understanding JWT Structure and How to Decode It — The Header, Payload & Signature — explained