Skip to content
cybersecurity authentication

JSON Web Token (JWT)

jwt authentication tokens api-security stateless
Plain English

A JWT is a tamper-proof ID badge your server issues after you log in. Instead of making you show your passport on every request, the server stamps a badge with your name and permissions, seals it with a secret wax stamp, and hands it to you. On each future request, you show the badge; the server verifies the wax stamp is genuine and reads your details without ever looking you up in a database again. If anyone modifies the badge, the stamp breaks and the server rejects it.

Technical Definition

A JSON Web Token (JWT), defined in RFC 7519, is a URL-safe compact representation of claims transferred between parties. A JWT consists of three Base64URL-encoded segments separated by dots:

<base64url-header>.<base64url-payload>.<base64url-signature>

# Concrete example (structure only -- not a valid token):
# header:    {"alg":"HS256","typ":"JWT"}
# payload:   {"sub":"user_123","exp":1713200000}
# signature: HMAC-SHA256(header + "." + payload, secret)

Header (algorithm and token type):

{ "alg": "HS256", "typ": "JWT" }

Payload (claims, not encrypted, only encoded):

ClaimNameDescription
issIssuerWho created the token (e.g., https://auth.example.com)
subSubjectWho the token is about (user ID)
audAudienceWho should accept the token
expExpirationUnix timestamp after which the token is invalid
iatIssued AtUnix timestamp when the token was created
nbfNot BeforeToken is invalid before this timestamp
jtiJWT IDUnique identifier, used for revocation

Signature (prevents tampering):

  • HS256 (HMAC-SHA256): symmetric; server signs and verifies with the same secret key
  • RS256 (RSA-SHA256): asymmetric; private key signs, public key verifies (safe for distributed systems)
  • ES256 (ECDSA): asymmetric, smaller keys than RSA

Security requirements:

  • Always validate exp before trusting the payload
  • Use RS256 or ES256 in distributed systems so microservices can verify without sharing secrets
  • Store JWTs in HttpOnly cookies (not localStorage) to prevent XSS theft
  • Implement token rotation: short-lived access tokens (15 min) + long-lived refresh tokens
  • Maintain a token revocation list (Redis blocklist) using the jti claim for logout

JWT creation, inspection, and validation

# Decode a JWT without verification (never trust unverified)
# Replace $TOKEN with a real token from your auth flow
$ echo "$TOKEN" | cut -d. -f2 | base64 -d 2>/dev/null | jq .
{
  "sub": "user_123",
  "role": "admin",
  "exp": 1713200000
}

# Verify signature and decode with jwt-cli
$ jwt decode --secret "$JWT_SECRET" "$TOKEN"
// Node.js: sign and verify JWTs
import jwt from "jsonwebtoken";

const ACCESS_SECRET = process.env.JWT_ACCESS_SECRET!;

export function signAccessToken(userId: string, role: string): string {
  return jwt.sign(
    { sub: userId, role },
    ACCESS_SECRET,
    { algorithm: "HS256", expiresIn: "15m" }
  );
}

export function verifyAccessToken(token: string): jwt.JwtPayload {
  // Throws on invalid signature, expired token, or malformed input
  return jwt.verify(token, ACCESS_SECRET) as jwt.JwtPayload;
}

// Middleware: validate JWT on protected routes
export function requireAuth(req: Request, res: Response, next: NextFunction): void {
  const token = req.cookies["access_token"];
  if (!token) { res.status(401).json({ error: "Unauthorized" }); return; }

  try {
    res.locals.user = verifyAccessToken(token);
    next();
  } catch {
    res.status(401).json({ error: "Invalid or expired token" });
  }
}
In the Wild

JWTs are everywhere in modern web auth. Auth0, Clerk, Supabase Auth, and Cognito all issue JWTs. The most common vulnerability is storing them in localStorage, which exposes them to any XSS script on the page. HttpOnly cookies are immune to JavaScript access. The second most common mistake is not validating exp, trusting a token indefinitely after it was issued. The “algorithm confusion attack” (setting alg: none in a forged header) was a real class of vulnerability in early JWT libraries; production libraries now reject none by default. For stateless API auth, JWTs eliminate the need for a session database lookup on every request, trading revocability for performance. The standard pattern is: 15-minute access JWT in a HttpOnly cookie, 7-day refresh token in a separate HttpOnly cookie, server-side revocation list for logout.