What Is a JWT Token? Here's What's Inside Those Strings
Photo by Safar Safarov on Unsplash
Table of Contents
The First Time I Saw a JWT, I Thought Something Was Broken
That's a JWT — a JSON Web Token. And once you understand what's inside it, you'll wonder why nobody explained it sooner. Because it's genuinely less mysterious than it looks.
I spent an embarrassing amount of time early in my career treating JWTs as magic black boxes. The backend team generated them, the frontend stored them, and I just passed them along in headers without understanding what I was actually sending. That changed the day a token expired mid-demo and I had to figure out *why* — which meant actually cracking one open and reading what was inside.
Turns out, that's surprisingly easy to do.
Three Chunks of JSON Glued Together — That's All It Is
Photo by Markus Spiske on Unsplash
`header.payload.signature`
That's it. No secret sauce. Each of the first two parts is just JSON data that's been Base64-encoded so it's URL-safe and compact. You can decode any JWT right now — paste one into a JWT decoder and you'll see plain JSON come out the other side.
**The Header** tells you which algorithm was used to sign the token:
```json { "alg": "HS256", "typ": "JWT" } ```
**The Payload** is where the actual data lives — who the user is, when the token expires, what they're allowed to do:
```json { "sub": "user_123", "name": "Jane Dev", "role": "admin", "iat": 1711612800, "exp": 1711699200 } ```
Those `iat` and `exp` values? Unix timestamps. If you've ever stared at one trying to figure out whether a token is expired, that converter turns those numbers into readable dates instantly.
**The Signature** is the security part. The server takes the header and payload, combines them, and hashes the result with a secret key. If anyone tampers with the payload — even changing a single character — the signature won't match, and the server rejects the token.
This is why you can't just edit a JWT's payload to give yourself admin access. You *can* edit it, but the signature verification fails instantly.
How JWT Auth Actually Works (In Normal Words)
1. You log in with your username and password. 2. The server checks your credentials. If they're valid, it creates a JWT containing your user ID, role, and an expiration time. It signs this token with a secret key that only the server knows. 3. The server sends the JWT back to you. 4. Your browser stores it (usually in localStorage or an httpOnly cookie). 5. Every API request after that includes the JWT in the `Authorization: Bearer <token>` header. 6. The server receives the request, verifies the JWT's signature, reads the payload, and knows who you are — without querying a database.
That last part is the key insight. With traditional session-based auth, the server stores session data and hits the database on every request to figure out who you are. With JWTs, all the information is baked into the token itself. The server just verifies the signature and reads the payload. No database lookup needed.
This is why JWTs became the default for APIs, single-page apps, and microservices. When you've got ten different services that all need to verify who a user is, sharing a session database between them is painful. Sharing a JWT signing key is trivial.
JWT vs Sessions — When Each One Makes Sense
**Use JWTs when:** - You're building APIs consumed by mobile apps or SPAs - You have multiple services that need to verify authentication independently - You want stateless auth where the server doesn't store session data - Your users authenticate once and make many subsequent requests
**Use sessions when:** - You need to instantly revoke access (logout should mean logout, right now) - You're building a traditional server-rendered web app - Your security model requires the server to control the session lifecycle completely - You don't want token data sitting in the browser where JavaScript can read it
The revocation problem is JWT's biggest weakness. When a user logs out with sessions, you delete the session from the server. Done. With JWTs, the token is still valid until it expires — you can't "un-sign" it. The workarounds (token blacklists, short expiration times, refresh token rotation) all add complexity.
I've watched teams adopt JWTs for everything because it was the hip choice, then spend weeks building the exact session-tracking infrastructure they were trying to avoid. Pick the right tool for the job.
JWT Mistakes That'll Bite You Eventually
Photo by FLY:D on Unsplash
**Storing sensitive data in the payload.** The payload is encoded, not encrypted. Anyone can decode a JWT and read its contents — it's literally just Base64. Never put passwords, credit card numbers, or anything confidential in there. Treat the payload like a postcard: anyone who intercepts it can read it.
**Setting absurdly long expiration times.** I've seen tokens with 30-day expirations. That means 30 days where a stolen token grants full access. Keep access tokens short-lived (15 minutes to an hour) and use refresh tokens to get new ones.
**Using `none` as the algorithm.** This was a real vulnerability in older JWT libraries. An attacker could set `"alg": "none"` in the header and the library would skip signature verification entirely. Modern libraries have patched this, but always validate the algorithm on the server side. The OWASP JWT cheat sheet covers this and other attack vectors.
**Not verifying the signature on every request.** Some developers decode the payload without verifying the signature first — "just to grab the user ID real quick." This defeats the entire purpose. If you're not verifying, you're trusting unverified data that came from the client. Always verify first.
**Confusing encoding with encryption.** Base64 encoding is not a security measure. It's a transport format. If you paste a JWT into any Base64 decoder, the payload appears in plain text. JWTs guarantee integrity (nobody tampered with it), not confidentiality (nobody can read it). If you need both, look into JWE (JSON Web Encryption).
JWT Claims Cheat Sheet
| Claim | Full Name | What It Does | |-------|-----------|-------------| | `sub` | Subject | Who the token is about (usually user ID) | | `iss` | Issuer | Who created and signed the token | | `aud` | Audience | Who the token is intended for | | `exp` | Expiration | When the token expires (Unix timestamp) | | `iat` | Issued At | When the token was created (Unix timestamp) | | `nbf` | Not Before | Token isn't valid before this time | | `jti` | JWT ID | Unique identifier for the token |
You can add any custom claims you want — `role`, `permissions`, `org_id`, whatever your app needs. Just keep the payload small. Every JWT gets sent with every request, and a bloated token eats bandwidth fast.
The complete JWT specification lives at jwt.io if you want the deep dive. For day-to-day work, a JWT decoder that shows the header, payload, and expiration in a clean format is all you really need. And if the payload contains nested JSON objects that need formatting, copy them into the JSON formatter for a cleaner view — I wrote about testing JSON formatters earlier this week.
Frequently Asked Questions
What is a JWT token and how does it work?
A JWT (JSON Web Token) is a compact, URL-safe string that carries authentication data between a client and server. It contains three Base64-encoded parts separated by dots: a header (signing algorithm), a payload (user data and claims like expiration time), and a signature (cryptographic proof the token hasn't been tampered with). The server creates and signs JWTs after login, and the client includes them in subsequent API requests.
Can you decode a JWT without the secret key?
Yes. The header and payload of a JWT are only Base64-encoded, not encrypted, so anyone can decode and read them using a JWT decoder. The secret key is only needed to verify the signature — to confirm the token hasn't been modified. This is why you should never store sensitive information like passwords in a JWT payload.
What is the difference between JWT and session-based authentication?
Session-based auth stores session data on the server and requires a database lookup on every request. JWT auth embeds all necessary data in the token itself, so the server only needs to verify the signature — no database query required. JWTs are better for APIs and microservices. Sessions are better when you need instant revocation or immediate logout.
How long should a JWT token last before expiring?
Access tokens should typically expire in 15 minutes to 1 hour. Longer expiration times increase the vulnerability window if a token is stolen. For longer sessions, use short-lived access tokens paired with refresh tokens that can be revoked server-side. Never set access token expirations measured in days or weeks.
Why is my JWT token being rejected by the server?
The most common reasons are: the token has expired (check the exp claim), the signature doesn't match because the server's signing key changed, the token was modified in transit, the algorithm in the header doesn't match what the server expects, or the token is being sent to the wrong service (check the aud claim). Paste the token into a JWT decoder to inspect its claims and expiration.
Is a JWT token encrypted?
No. Standard JWTs (JWS — JSON Web Signature) are signed but not encrypted. The payload is Base64-encoded, which is a reversible encoding, not encryption. Anyone can read the contents. If you need the payload to be unreadable by third parties, use JWE (JSON Web Encryption), which encrypts the payload so only authorized parties can decrypt it.
Try ToolsFuel
23+ free online tools for developers, designers, and everyone. No signup required.
Browse All Tools