Back to Blog
Guide11 min read2026-07-04

JWT Authentication Explained for Developers

JWTs are compact, signed tokens that carry claims about a user. This guide explains their structure, how signing works, the stateless tradeoff, and the security mistakes that bite teams.

Ajay Kumar
Ajay Kumar
Founder & DevOps, PandaStack

# 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:

TypeAlgorithmKey model
SymmetricHS256One shared secret signs and verifies
AsymmetricRS256 / ES256Private 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 request

The 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 sessionsJWTs
ValidationDB/cache lookupSignature check (no lookup)
RevocationInstant (delete session)Hard (valid until expiry)
ScalingNeeds shared session storeStateless, easy
Token sizeTiny (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: none attack. Old/naive libraries accepted tokens with alg set to none (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), and nbf (not-before) — not just the signature.
  • Storing tokens in localStorage. Vulnerable to XSS. Prefer HttpOnly, Secure, SameSite cookies 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).

Ready to deploy?

Start free on PandaStack.

Start free on PandaStack

More in Guide

Browse all Guide articles →

See also