TK
All writing
Backend

JWT authentication explained simply

What a JWT actually is, how token-based auth flows end to end, access vs refresh tokens, where to store them (and why localStorage is risky), and the mistakes that make JWT auth insecure.

Tanveer Khan10 min read

JWT auth is everywhere and misunderstood everywhere. A JWT isn't magic and isn't encryption — it's a signed, readable token. Understanding that one fact prevents most of the security mistakes people make with it.

What a JWT is

Three base64url parts separated by dots: header.payload.signature. The payload is just JSON — anyone can read it. The signature is what matters: it proves the token was issued by your server and hasn't been tampered with.

A JWT is signed, not encrypted. The payload is readable by anyone who has the token. Never put secrets (passwords, card numbers) in it — only non-sensitive claims like a user id and role.

The flow

  • User logs in with credentials → server verifies them.
  • Server signs a JWT with its secret and returns it.
  • Client sends the token on each request (Authorization: Bearer <token>).
  • Server verifies the signature — no DB lookup needed — and trusts the claims.
auth.ts
ts
import jwt from "jsonwebtoken";

// issue
const token = jwt.sign({ sub: user.id, role: user.role }, process.env.JWT_SECRET!, {
  expiresIn: "15m",
});

// verify (middleware)
const payload = jwt.verify(token, process.env.JWT_SECRET!);
req.userId = payload.sub;

Access vs refresh tokens

Short-lived access tokens (minutes) limit damage if one leaks. A long-lived refresh token (stored securely, server-revocable) mints new access tokens so the user isn't logged out every 15 minutes.

Where to store them — the real debate

  • localStorage: convenient but readable by any XSS-injected script. A stolen token is a stolen session.
  • httpOnly cookie: not reachable from JavaScript (XSS-safe), but you must defend against CSRF (SameSite + a CSRF token).
  • Common pattern: refresh token in an httpOnly cookie, access token in memory only.

Always set a short expiry, verify the signature AND expiry on every request, and have a server-side way to revoke refresh tokens (a denylist or rotating token family). A JWT you can't revoke is a liability.

A JWT is a readable, signed claim about who the user is. Keep access tokens short-lived, store them carefully, never trust the payload without verifying the signature, and keep a revocation path. Get those right and token auth is both simple and secure.