# JWT Authentication Explained for Developers
JSON Web Tokens (JWTs) are everywhere in modern auth, and also widely misused. They're a compact, self-contained way to carry verified claims between parties — but their stateless nature creates real tradeoffs that trip up a lot of developers. Let's understand what a JWT actually is, how it works, and where the sharp edges are.
What a JWT is
A JWT is a string with three base64url-encoded parts separated by dots:
header.payload.signature
eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjMiLCJyb2xlIjoiYWRtaW4ifQ.4pEr...- Header — metadata, including the signing algorithm (
alg):
`json
{ "alg": "HS256", "typ": "JWT" }
`
- Payload — the *claims* (data about the subject):
`json
{ "sub": "123", "role": "admin", "exp": 1735689600 }
`
- Signature — a cryptographic signature over the header and payload, proving the token wasn't tampered with.
Crucial point: the header and payload are encoded, not encrypted. Anyone can decode and read them. Never put secrets (passwords, sensitive PII) in a JWT payload.
How the signature works
The server signs base64url(header) + "." + base64url(payload) with a key. On each request, it recomputes the signature and compares. If they match, the token is authentic and unmodified — without a database lookup. Two families of algorithms:
| Type | Algorithm | Key model |
|---|---|---|
| Symmetric | HS256 | One shared secret signs and verifies |
| Asymmetric | RS256 / ES256 | Private key signs, public key verifies |
HS256 is simplest (one secret) but anyone who can verify can also sign — fine within one service. RS256 lets you distribute the *public* key to many services to verify tokens while only the auth server holds the private signing key. Use asymmetric when multiple services need to verify.
The standard auth flow
1. User logs in (email/password)
2. Server verifies credentials, issues a signed JWT
3. Client stores the token, sends it on each request:
Authorization: Bearer eyJ...
4. Server verifies the signature + claims, authorizes the requestThe appeal: in step 4 the server validates the token *cryptographically* without hitting a session store. That's the stateless property — and the source of both JWT's benefits and its problems.
The stateless tradeoff (the part people miss)
Because the server doesn't store sessions, it also can't easily *revoke* them. A traditional session lives in a database — delete the row, the user is logged out instantly. A JWT is valid until it expires, signature alone. So if a token is stolen, or you need to force-logout a user, or someone's permissions change, a plain JWT keeps working until exp.
This is *the* central JWT tradeoff:
| Server-side sessions | JWTs | |
|---|---|---|
| Validation | DB/cache lookup | Signature check (no lookup) |
| Revocation | Instant (delete session) | Hard (valid until expiry) |
| Scaling | Needs shared session store | Stateless, easy |
| Token size | Tiny (just an ID) | Larger (carries claims) |
The access + refresh token pattern
The standard mitigation is two tokens:
- A short-lived access token (e.g. 15 minutes) — used on every request. Its short life bounds the damage if stolen.
- A long-lived refresh token (e.g. days) — stored securely, used only to obtain new access tokens. Refresh tokens *can* be tracked/revoked server-side because they're used rarely.
When the access token expires, the client silently exchanges the refresh token for a new one. Revocation happens by invalidating the refresh token; the access token dies on its own shortly after.
Common security mistakes
JWT footguns are well-documented — and frequently exploited:
- The
alg: noneattack. Old/naive libraries accepted tokens withalgset tonone(no signature). Always pin the expected algorithm server-side; never trust the token's own header to decide. - Algorithm confusion (RS256 → HS256). An attacker submits an HS256 token signed with your *public* key, and a sloppy verifier uses the public key as an HMAC secret. Pin the algorithm.
- Not validating claims. Verify
exp(expiry),iss(issuer),aud(audience), andnbf(not-before) — not just the signature. - Storing tokens in
localStorage. Vulnerable to XSS. PreferHttpOnly,Secure,SameSitecookies for the refresh token at least. - Putting secrets in the payload. It's readable by anyone. Don't.
- Long-lived access tokens with no refresh strategy — maximizes the window of a stolen token.
When to use JWTs (and when not to)
JWTs shine for:
- Stateless APIs and microservices where avoiding a shared session store is valuable.
- Cross-service / cross-domain auth where multiple services verify the same token.
- Service-to-service auth and short-lived authorization tokens.
They're often *overkill* for a simple server-rendered monolith, where a traditional session cookie is simpler, smaller, and trivially revocable. Don't reach for JWTs reflexively — pick them when their stateless property earns its tradeoffs.
Verifying a JWT (sketch)
import jwt from 'jsonwebtoken';
function verify(token) {
// Pin the algorithm; validate issuer and audience
return jwt.verify(token, PUBLIC_KEY, {
algorithms: ['RS256'],
issuer: 'https://auth.example.com',
audience: 'my-api',
});
// throws on bad signature, wrong alg, or expired token
}Auth on PandaStack
PandaStack uses token-based authentication internally (Bearer tokens) for its API and supports SSO, teams/orgs, and role-based access control (owner/admin/member) for managing who can do what across your projects. For your *own* apps, you implement JWT or session auth in your code as usual — and PandaStack gives you the pieces around it: environment variables for your signing keys and secrets (never hardcode them), managed Redis (via KubeBlocks) if you want a revocation/refresh-token store, and automatic SSL so tokens are never sent over plaintext.
References
- [RFC 7519 — JSON Web Token (JWT)](https://www.rfc-editor.org/rfc/rfc7519)
- [jwt.io — introduction and debugger](https://jwt.io/introduction)
- [OWASP — JWT security cheat sheet](https://cheatsheetseries.owasp.org/cheatsheets/JSON_Web_Token_for_Java_Cheat_Sheet.html)
- [Auth0 — refresh tokens](https://auth0.com/docs/secure/tokens/refresh-tokens)
- [RFC 8725 — JWT best current practices](https://www.rfc-editor.org/rfc/rfc8725)
Building auth into your app? Store your signing keys as env vars and ship securely on PandaStack's free tier with automatic SSL. [Start at dashboard.pandastack.io](https://dashboard.pandastack.io).