What is a JSON Web Token and How Does It Work?
A JSON Web Token (JWT, pronounced 'jot') is a compact, self-contained mechanism for securely transmitting information between parties as a digitally signed string. Defined in RFC 7519, a JWT consists of three Base64URL-encoded segments: the Header specifies the signing algorithm (alg) and token type (typ). The Payload contains claims — structured assertions about the subject (user, service) plus metadata like expiry time and issuer. The Signature is the cryptographic proof created by applying the algorithm from the header to the encoded header + payload using a secret or private key. The three parts are concatenated with dots: base64url(header).base64url(payload).signature. The signature makes the token tamper-evident: changing even one character in the header or payload produces a completely different signature that fails verification. This self-contained verifiability is what makes JWTs so useful for distributed systems — any service with the verification key can independently confirm the token's integrity without querying the issuing service.
Choosing the Right JWT Algorithm: HS256, RS256, or ES256?
Algorithm choice is the single most important JWT design decision. The rule of thumb: if all services that need to verify the token are under your control and can share a secret securely, use HS256. If you need third-party services to verify tokens without sharing a private key — or if you're implementing OAuth 2.0 / OpenID Connect — use RS256 or ES256. HS256 is the simplest and fastest: one secret, both signs and verifies. The weakness is that every verifying service needs the secret. If any service is compromised, the secret must be rotated across all services. RS256 solves this with asymmetry: only the auth server holds the private key. Any service can verify using the freely distributable public key. The cost is token size (RSA signatures are ~256 bytes) and generation speed. ES256 (ECDSA P-256) achieves the same asymmetric security with a 64-byte signature — four times smaller than RS256 — using elliptic curve cryptography. For new systems, ES256 is the modern recommendation: compact, fast, and asymmetric.
JWT Claims: What to Include and What to Avoid
JWT claims are the heart of the token — they define who the token is about, what it authorizes, and when it expires. Registered claims from RFC 7519 have standardized meanings: sub identifies the subject (typically a user ID), iss identifies the issuer (your auth service URL), aud specifies the intended audience (the API or service), exp defines the expiry time (Unix epoch seconds), iat records issuance time, and jti provides a unique token ID for replay prevention. Custom (private) claims go beyond the standard: role or roles for RBAC, scope or permissions for fine-grained authorization, tenant_id or org_id for multi-tenant apps, email or name for convenience. Critical security note: the JWT payload is Base64URL-encoded — trivially decoded by anyone with the token. Never include passwords, social security numbers, credit card data, or other sensitive PII. If you need encrypted claims, use JWE (JSON Web Encryption) instead.
JWT Token Lifetime: Balancing Security and User Experience
Token expiry is a fundamental security control — without it, a stolen token is valid forever. But too-short expiry creates a poor user experience. The industry-standard pattern is a two-token system: a short-lived access token (15 minutes to 1 hour) used on every API request, and a longer-lived refresh token (7–30 days) stored securely (HttpOnly cookie) that is only used to request new access tokens. When the access token expires, the client silently exchanges the refresh token for a new access token and refresh token (refresh token rotation). This gives security (access tokens expire quickly, limiting damage from theft) and good UX (users stay logged in for days or weeks). For high-security applications like financial services: 15-minute access tokens, 8-hour refresh tokens. For standard web apps: 1-hour access tokens, 30-day refresh tokens. For machine-to-machine: 60-second tokens renewed automatically.
JWT in OAuth 2.0 and OpenID Connect
OAuth 2.0 and OpenID Connect (OIDC) are the most common contexts where JWTs appear in production. In OAuth 2.0, the authorization server issues access tokens that resource servers use to authorize API calls. Many OAuth implementations use JWTs for access tokens (though the spec doesn't require it) because they're self-contained — resource servers can verify them without querying the authorization server. OpenID Connect (built on OAuth 2.0) mandates JWTs for its ID token — a token containing identity claims about the authenticated user. The ID token's iss identifies the OpenID Provider, sub is the user's stable ID, aud is the client application, exp defines token expiry, and additional claims like email, name, and picture are commonly included. For OAuth 2.0 / OIDC, RS256 is the standard algorithm choice because the public key can be published at a .well-known/jwks.json endpoint — any consumer can verify tokens without any pre-shared secret.
Common JWT Security Pitfalls and How to Avoid Them
JWT security vulnerabilities are often implementation mistakes rather than flaws in the standard itself. The alg:none vulnerability: some early libraries accepted a token with 'alg: none' in the header as valid, bypassing signature verification entirely. Always specify the expected algorithm explicitly in your verifier — never trust the alg claim from the token. The algorithm confusion attack: A library configured for RS256 that also accepts HS256 can be attacked by signing a forged token with the public key as the HMAC secret. Always use algorithm whitelisting. Weak secrets: HS256 signatures can be brute-forced offline if the secret is short or guessable. Use cryptographically random secrets of at least 256 bits. Sensitive data in payload: The payload is encoded, not encrypted. Never put passwords, PII, or other sensitive data in JWT claims — use JWE if you need encrypted payload content. Missing claims validation: A valid signature on an expired or wrong-audience token should still be rejected. Always validate iss, aud, exp, and nbf after verifying the signature. JWT in localStorage: Tokens stored in localStorage are accessible to JavaScript and vulnerable to XSS. Prefer HttpOnly cookies for sensitive tokens in browser-based applications.