The first time I shipped JWT auth, I copied a middleware snippet, watched the login work, and moved on. Six months later a teammate asked why we could read a user's email by pasting the token into jwt.io. I didn't have a good answer. I'd used JSON Web Tokens for half a year without understanding the one thing that mattered: a JWT is signed, and that's all. It isn't secret.
That gap is common. JWT shows up in almost every web stack, but most of us learn it in pieces, on deadline, from Stack Overflow answers that disagree with each other. Then we forget it until the next auth bug. Here's how I'd learn it again if I were starting over, and how to make it stick this time.
Start with the shape, because it explains everything
A JWT is three base64url strings joined by dots: header, payload, signature. The header says which algorithm signed it, like {"alg":"HS256","typ":"JWT"}. The payload holds claims, which are just JSON fields. The signature is computed over the first two parts with a key.
Decode the first two parts yourself once. Use atob in a browser console after swapping the URL-safe characters, or paste a test token into a decoder. When you see your own payload come back as plain JSON, the main lesson lands on its own: base64url is encoding, and anyone holding the token can read it.
So don't put anything in a payload you wouldn't print in a log. No passwords, no internal IDs you'd rather keep private, no personal data you don't need.
Signing is not encryption
This is the part people blur. A signed token (the spec calls it JWS) proves that whoever holds the key created it and that nobody changed it. An encrypted token (JWE) hides the contents. Almost every JWT you'll meet in a login flow is the signed kind.
The two algorithm families are worth one sentence each. HS256 uses a single shared secret, so the server that signs is also the server that verifies. RS256 and ES256 use a key pair: the auth server signs with a private key, and any service can verify with the public key. If you have more than one service checking tokens, the key-pair version saves you from copying a secret everywhere.
The claims you should actually check
The spec, RFC 7519, lists a handful of registered claims. You don't need to memorize all of them. You need to verify these on every request:
-
exp, the expiry time. Reject expired tokens. -
iss, who issued it. Make sure it's your auth server. -
aud, who it's meant for. A token minted for one API shouldn't work on another.
sub usually holds the user ID, and iat says when the token was issued. Libraries check exp by default, but many leave aud and iss to you, and that's where real bugs live.
One more: never trust the alg in the header to choose how you verify. Pin the algorithm on the server. The old alg: none trick, and the one where an attacker switches RS256 to HS256 and signs with your public key, both work only when the server lets the token decide.
The trade-off nobody mentions in tutorials
JWTs are stateless. The server doesn't look anything up, which is fast and simple. It also means you can't easily take a token back. If a user logs out or gets banned, a token with a 24-hour expiry keeps working for 24 hours.
The usual fix is a short-lived access token (a few minutes) plus a refresh token stored server-side that you can revoke. Once you see that pattern, half of the "JWT vs sessions" arguments online make more sense. People aren't disagreeing about JWT. They're disagreeing about how much state they're willing to keep.
Where to store tokens in a browser is the other long argument. An httpOnly cookie keeps JavaScript from reading the token, which helps against XSS, but then you need to think about CSRF. localStorage is simpler and fully exposed to any script on the page. Pick one on purpose and write down why.
Why it doesn't stick, and what helped me
I've "learned" JWT at least three times. Each time I read a good blog post, nodded, and forgot the details by the next project. The issue wasn't the material. I never saw it again until I needed it, and by then it was a stranger.
What finally worked was treating auth notes the way I'd treat anything I study: save the explainer once, keep a short summary in my own words, and get asked about it a few days later.
I do this in Recl, which I'm building. I save the article or conference talk, and it gives me a summary with key takeaways. Then it files the save under a topic, so my auth material sits together.
The part that matters for retention is the recall nudge. A few days after saving, I get a question like "Still remember this one?" and have to answer from memory. "Which claim stops a token for one API from working on another?" is much harder to fake than rereading a highlight. When I miss, the gap shows up before a code review finds it.
You can do a low-tech version too. Write five questions on a card and check yourself on day 1, day 3 and day 7. Spaced repetition doesn't need software. Software just stops you from skipping it.
A short study plan
Spend one evening decoding real tokens and reading the payloads. Spend another wiring up verification in your stack with exp, iss and aud checks and a pinned algorithm. Then break it on purpose: send an expired token, a token for the wrong audience, a token with a changed payload. Watch each one fail.
After that, write three sentences you want to remember a month from now. Mine are: the payload is readable by anyone; the server picks the algorithm, not the token; short access tokens plus revocable refresh tokens.
If you want to see what a saved, summarized technical explainer looks like, a few are on the public Recl library. Either way, get the basics out of the snippet and into your head before the next teammate asks you about jwt.io.
Asutosh builds Recl, an app for saving what matters and recalling it later. It turns the articles and videos you'd otherwise forget into summaries, library topics and spaced recall.


Top comments (0)