You know that moment when a login call succeeds and your browser's Network tab shows a 400-character string that looks like somebody sneezed on a keyboard? eyJhbGciOi... forever. That's a JWT, and despite looking like a nuclear launch code, it's actually three small pieces of JSON glued together with dots. Once you know what's in there, JWTs stop being scary and start being obvious — and you can pop any token into our JWT Decoder & Encoder to see exactly what a server is sending your user. This guide walks you through the anatomy of a JWT, how the signature actually proves anything, which algorithms to use, and the traps that have ended careers.
If you've been pasting tokens into random websites to "just see what's inside," stop. By the end of this post you'll know exactly what's going on and why you should really be decoding them on a tool that runs entirely in your own browser.
So What Is a JWT, Really?
A JSON Web Token is a way to hand someone a piece of information — usually "this person is logged in and here's who they are" — in a format that the recipient can verify hasn't been tampered with, without having to call back to a database. It's standardised in RFC 7519. The whole point is portability: a signed JWT is self-contained. Any server with the verification key can trust it on sight. No session store, no Redis lookup, no round trip.
Physically, a JWT is just three base64url-encoded strings separated by dots: header.payload.signature. The first two are regular JSON objects you can decode trivially — base64url isn't encryption, it's just encoding. Anyone can read the contents. The signature is the only part that's cryptographically interesting: it's a hash (for HMAC) or a digital signature (for RSA/ECDSA/EdDSA) of the first two parts combined with a secret or private key. If even one character changes, the signature no longer matches, and the server rejects the token.
How a JWT Is Actually Built, Step by Tiny Step
Understanding the construction demystifies the whole thing. Say you want to issue a token for user 42. You start by writing two little JSON objects:
header = { "alg": "HS256", "typ": "JWT" }
payload = { "sub": "42", "name": "Ada", "iat": 1744372800, "exp": 1744376400 }
h = base64url(JSON.stringify(header))
p = base64url(JSON.stringify(payload))
signature = HMAC-SHA256(h + "." + p, SECRET)
s = base64url(signature)
token = h + "." + p + "." + s
That's the whole algorithm for HS256. The clever bit is the signature — it's a keyed hash, so only someone who knows the secret can generate it. When the token comes back on a later request, your server recomputes the HMAC over h + "." + p using the same secret and checks that the result matches the s attached to the token. If anyone messed with the payload in transit — changed "sub":"42" to "sub":"1", for instance — the recomputed hash won't match and verification fails. Simple, fast, and remarkably resistant to forgery as long as you keep the secret secret.
RS256 (and its siblings) work similarly but use asymmetric cryptography. Instead of one shared secret, you sign with a private key and anyone verifies with the public key. This is what lets OAuth providers like Google or Auth0 publish a public key (at a jwks_uri) that every API in the world can use to verify tokens, without any of those APIs ever touching the private key. It's the backbone of federated identity.
A JWT isn't encrypted. It's signed. Anyone can read what's inside — the only thing the signature prevents is changing what's inside.
A Real Token, Decoded Live
Let's take a token and walk through what you'd see in our decoder. Suppose you paste this in (pretend the dots separate real base64url blobs):
eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiI0MiIsIm5hbWUiOiJBZGEiLCJpYXQiOjE3NDQzNzI4MDAsImV4cCI6MTc0NDM3NjQwMH0.yLu9hH7x...
The decoder splits on the dots, base64url-decodes the first chunk, and shows you {"alg":"HS256"} in the header pane. It decodes the second chunk and shows you a clean payload with four claims: sub (the user id), name, iat (issued at), and exp (expires at). Because iat and exp are Unix timestamps — an annoying thing to read by eye — a good decoder will render them as human dates too: "issued Apr 11 2026, 14:40 UTC; expires Apr 11 2026, 15:40 UTC." So at a glance you can tell this token is good for one hour.
Now comes the interesting part. You paste the server's signing secret into the verification field, and the decoder recomputes the HMAC on the spot. Green check: signature verified. Swap the secret for the wrong one: red cross, signature invalid. Change a character in the payload: red cross, signature invalid. This is the moment JWTs "click" for most people — you can literally watch the tamper detection happen.
Our tool goes the other way too. Flip to encoder mode, edit the header and payload as plain JSON, type a secret, and a signed token pops out the other end, ready to paste into a curl call or a test. Perfect for reproducing a bug report or testing how your API reacts to an expired token ("exp" in the past — try it).
Common JWT Mistakes (The Ones That Hurt)
JWTs are not complicated but they are subtle, and the mistakes below have shown up in real post-mortems more times than anyone likes to admit.
- Storing secrets in the payload. Say it with me: payloads are not encrypted. Anyone with the token can read it. Never put passwords, credit card numbers, or anything you'd be unhappy to see on Twitter in a JWT. Claims are for identity, not privacy.
- Trusting the
algfield. One of the most famous JWT vulnerabilities (CVE-2015-9235 and friends) was servers that read thealgheader and used whatever algorithm the token claimed. An attacker could changealgtononeand skip signature checks. Always pin the algorithm your verifier accepts — don't let the token negotiate. - Using HS256 with a weak secret. HS256 depends entirely on the secret's entropy.
"password123"can be brute-forced in seconds. Use at least 256 random bits — the tool's placeholder text literally says "a-string-secret-at-least-256-bits-long" for a reason. - Skipping the
expclaim. A JWT without an expiration lives forever. If it leaks, the attacker has permanent access. Always setexp, and keep it short for access tokens (minutes, not days). - Rotating secrets without a grace period. When you rotate an HMAC secret, every outstanding token instantly breaks and every logged-in user bounces to the login screen. Support multiple valid keys during rotation, or phase them in over a deliberate window.
- Storing JWTs in localStorage without thinking. Any XSS in your app can read localStorage. For tokens that matter, prefer HttpOnly cookies with the Secure and SameSite flags, and design around CSRF separately.
Key Terms You'll See in Every JWT Conversation
- Header: The first segment. A small JSON object declaring the algorithm (
alg) and token type (typ). - Payload: The second segment. A JSON object containing claims about the subject — user id, roles, expiry, etc.
- Signature: The third segment. A cryptographic proof that the header and payload haven't been altered since the issuer signed them.
- Claim: A single key-value statement in the payload. Some claim names are reserved by RFC 7519 (registered claims); others are private to your application.
iss,sub,aud: Issuer, subject, audience. Who made the token, who it's about, and which service is allowed to consume it.iat,nbf,exp: Issued-at, not-before, and expiration — all Unix timestamps.nbflets you issue a token that activates in the future.- HS256 / HS384 / HS512: HMAC with SHA-2. Symmetric: one shared secret signs and verifies. Fast, simple, good for monoliths.
- RS256 / ES256 / EdDSA: Asymmetric signatures using RSA, elliptic curves, or Edwards curves. Private key signs, public key verifies. Essential for multi-service architectures and OAuth providers.
- JWKS (JSON Web Key Set): A published set of public keys, usually at
/.well-known/jwks.json, that verifiers can fetch to validate tokens signed by a remote issuer.
How to Use Our JWT Decoder in 30 Seconds
- Paste a token into the input field in decoder mode. The decoder splits it on the dots and fills the header and payload panes instantly.
- Review the claims. Switch to the Claims Breakdown view to see standard claims annotated with their meanings and timestamps converted to readable dates.
- Verify the signature by entering the HMAC secret or public key. Green means the token is genuine and untampered.
- Flip to encoder mode when you want to build a token. Edit the header, edit the payload as JSON, paste a secret, and copy the generated token.
- Explore algorithms. Switch between HS256, RS256, ES256, and friends to see how the same payload produces completely different signatures.
- Never paste real production tokens. Even though everything runs in your browser, train the habit: use test tokens for exploration, real ones only for real investigations.
Got a mystery token in your hands?
Paste it into the decoder, verify the signature, and see every claim in plain English. Runs entirely in your browser — nothing sent anywhere.
Open the JWT DecoderFrequently Asked Questions
Should I use JWTs or old-fashioned session cookies?
Honestly? For most web apps, boring server-side sessions are still the right call. JWTs shine when you have multiple services that need to validate identity without sharing a session store, when you're doing mobile-to-API auth, or when you're integrating with an OAuth provider. For a standard three-tier web app with a single backend, a session cookie is simpler, easier to revoke, and has fewer footguns.
Can I revoke a JWT once it's issued?
Not easily, and this is JWT's dirtiest secret. Because they're self-validating, a signed token remains valid until its exp passes, even if the user logged out or changed their password. The workarounds are a short-lived access token plus a long-lived refresh token, or a server-side revocation list that you check on every request — which ironically reintroduces the database lookup JWTs were supposed to eliminate. Design for this upfront.
What's the difference between HS256 and RS256 in practice?
HS256 uses a single shared secret for both signing and verification — fine when the same service does both. RS256 uses a private/public key pair, so you can hand out the public key to any verifier on earth while keeping the private key in one secure place. Rule of thumb: HS256 if signer and verifier are the same codebase; RS256 (or ES256 for smaller tokens) if not.
How big should my JWTs be?
As small as you can make them. Every JWT rides along on every request, and anything above a kilobyte starts eating into HTTP header budgets. Keep the payload lean: user id, a few roles, expiry, and that's usually enough. Pull everything else from the database on the server side. If your JWT is 4 KB, you're using it wrong.
Is ES256 better than RS256?
For new systems, generally yes. ES256 signatures are much smaller (about 64 bytes vs 256 for RS256), which means shorter tokens, and signing is faster. RS256 is more universally supported by older libraries, so if you're integrating with legacy systems you may be stuck with it. EdDSA (Ed25519) is even better when supported — it's the modern default.
Can two different secrets produce the same signature?
Effectively no. HMAC-SHA256 has a 256-bit output, so the probability of a collision between two different secret-and-message pairs is astronomically small. That's what "cryptographically secure" means in practice — not that it's impossible, but that the universe will end first.
Why does my JWT fail verification even though it looks fine?
Eight times out of ten, it's clock skew — the verifier's system time is slightly ahead of the issuer's, and a token that's "expired by 3 seconds" gets rejected. Most libraries expose a clockTolerance option for exactly this. Other culprits: trailing whitespace in the secret, using the wrong encoding (base64url vs plain base64), or a mismatched key pair for RS256.
The One Thing to Take Away
A JWT is three small strings glued with dots, two of which are plain JSON anyone can read, and one of which is a signature that makes tampering detectable. That's the entire mental model. Everything else — the algorithms, the registered claims, the OAuth rituals — is detail built on that foundation. Use our JWT Decoder & Encoder whenever a token confuses you, and use it locally, in your browser, where tokens belong. And please, for the love of everything secure: pin your algorithm, set a short expiry, and never put anything in the payload you wouldn't print on a T-shirt.