An Interactive Explainer

How JWT Actually Works

Tokens, tamper-proofing, and why your server trusts a string of random-looking characters.

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOjQyLCJyb2xlIjoiYWRtaW4iLCJleHAiOjE3MDAwMDAwMDB9.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
▼ scroll to begin

Act I

The Server Has No Memory

HTTP is amnesiac. Every single request arrives at the server completely fresh — no context, no history, no idea who sent it. This is by design, and it's what makes the web scale so well. But it creates an obvious problem: how does a server know you're you after you've logged in?

Your browser sends a username and password once. The server checks them and says "yep, that's Alex, user #42." And then… the connection closes. The next request arrives and the server has already forgotten. It has no idea who's knocking.

Client🧑 You
GET /dashboard
Who are you?? 🤷
Server🖥️ API
Press a button below to simulate a request
click the buttons below to try different requests
Simulate:

So we need some way to carry identity from request to request. The oldest solution is sessions: after login, the server creates a record in its database ("session abc123 = user 42"), puts that ID in a cookie, and checks it on every request. It works! But it requires the server to maintain state — that database of active sessions. On big systems with millions of users, that becomes its own headache.

What if the server didn't need to remember anything at all?

Act II

The Naive Fix — and Why It Fails

Here's the simplest idea: just have the client tell the server who they are on every request. No session database needed — the identity travels with the message.

Try it below. You've just logged in as a regular user. Edit the request fields and see what the server believes…

Your claimed User ID
Your claimed Role
✓ Server believes: "Hello, user 42! You have user privileges."
Presets:

Right. If the server just trusts whatever the client says, anyone can claim to be anyone. Set userId to 1 and suddenly you're the admin. The server has no way of knowing if the data was tampered with.

We need a way for the server to verify that the data it receives is exactly what it issued — and that nobody touched it in between.

This is where cryptography earns its keep.

Act III

Signing: Proving a Message Is Untouched

Here's the key insight — and it's a beautiful one. Instead of the server remembering what it issued, it can verify any message it receives by running a mathematical function on it.

The function is called HMAC — Hash-based Message Authentication Code. Here's how it works: you feed it a message and a secret key, and it produces a fixed-length fingerprint. Change even one character of the message and you get a completely different fingerprint. Without the key, you can't produce a valid fingerprint at all.

The server holds the secret key. Clients never see it. Let's watch this in action:

💬
Message (try editing this)
🔑
Secret key (only the server knows this)
HMAC-SHA256( message + key ) =
computing...
edit the message above — watch the signature completely change
Try:
Try it Change just one character in the message — a lowercase letter to uppercase, a comma to a period. The entire signature changes completely. This is called the avalanche effect, and it's the property that makes HMAC so useful. There's no way to predict the output without running the function.

Now here's how a server uses this: when it issues a token, it signs the data with its secret key and appends the signature. When the token comes back in a future request, the server re-signs the data it received and compares. If the signatures match — it's the same data, untouched. If they don't — something was changed.

Act IV

Anatomy of a JWT

A JSON Web Token is just this idea in a standardised format. It's a string with three parts, separated by dots:

HEADER . PAYLOAD . SIGNATURE

Each part is just Base64url encoded — a way of representing bytes as plain text characters that are safe to put in headers and URLs. It's not encryption (we'll come back to that). Click each part below to see what's inside:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOjQyLCJyb2xlIjoidXNlciIsImV4cCI6MTcwMDAwMDAwMH0.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
① Header
② Payload
③ Signature
Click each part to decode it

The header tells the server which algorithm to use for verification. The payload holds your actual data — user ID, role, expiry time, whatever the application needs. The signature is HMAC applied to header + "." + payload using the server's secret key.

Notice the payload has an exp field — an expiry timestamp. JWTs are self-contained, so they carry their own expiry. The server doesn't need to look anything up; it just checks if the current time is before exp.

Act V

Validation — The Server's Side

This is the main event. When a JWT arrives at the server, here's the exact sequence:

Split on the dots. Take the first two parts (header + payload). Re-run HMAC with the secret key. Compare the result to the signature in the third part. If they match — the data is genuine. If they don't — someone tampered with it, and the request gets rejected.

Try breaking the token below:

① Incoming payload
{"userId":42,"role":"user","exp":1700000000}
② Server re-signs
computing…
③ Token's signature
SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
Try tampering:
The core insight Try every tamper option. Notice that no matter how you change the payload, the computed signature never matches — because you don't have the server's secret key. The only way to produce a valid signature for modified data would be to know the secret. And you don't.

This is why JWTs are stateless. The server doesn't store anything. It doesn't look up sessions. It just does math — a hash function that takes about a microsecond — and either accepts or rejects the request. A server handling millions of requests can validate tokens without touching a database at all.

Act VI

One Important Caveat: Anyone Can Read This

Here's something that trips people up. Base64 encoding looks like gibberish, but it's not secret at all — it's just a way of encoding bytes into safe text characters. Anyone who gets their hands on a JWT can decode the payload and read everything inside.

Try decoding a payload yourself:

Base64url encoded (what travels on the wire)
eyJ1c2VySWQiOjQyLCJyb2xlIjoidXNlciIsImV4cCI6MTcwMDAwMDAwMH0
Decoded (perfectly readable JSON)
{"userId":42,"role":"user","exp":1700000000}
Or paste your own Base64url string:

So: anyone who intercepts a JWT can read the payload in plain text. Never put sensitive data — passwords, credit card numbers, personal details — inside a JWT payload. The signature proves the data wasn't tampered with; it doesn't hide the data.

If you need to hide the payload, you'd use JWE (JSON Web Encryption), which actually encrypts the contents. Most applications don't need this — they use JWTs over HTTPS, which encrypts the whole connection, so the token is private in transit. The payload being readable at rest is usually fine.

JWTs are about integrity, not secrecy. The signature proves the data is untouched. HTTPS provides privacy in transit. These are different problems with different solutions — and JWT only solves one of them.

Putting It Together

The Full Picture

Here's the complete JWT flow, end to end:

User sends: username + password
Server checks credentials, creates payload, signs it → issues JWT
Client stores JWT (typically in memory or httpOnly cookie)
Every subsequent request includes it in the Authorization: Bearer <token> header
Server receives request → splits JWT → re-signs header+payload → compares
Match: trusted. Extract userId, role, etc. Handle request.
No match: 401 Unauthorized. Done.
When exp passes, token is expired. Client must re-authenticate.
(Often done transparently with a separate refresh token)

And that's how your Nintendo Switch (or any modern API) can trust an incoming request without querying a session database. The math is the memory.

One small caveat we glossed over: unlike sessions, JWTs can't be easily invalidated before they expire. If a user's token is stolen, or they change their password, the old tokens are still technically valid until they expire. Most production systems handle this with short expiry times (15 minutes) and a separate revocation mechanism — but that's a story for another day.