What is a JWT (JSON Web Token)?
JWT explained: structure, authentication flow, and security best practices.
A JWT (JSON Web Token) is a compact, self-contained string used to securely transmit information between parties — most commonly, to prove who a user is after they log in. Instead of the server remembering every logged-in user, the server issues a signed token that the client presents on every subsequent request.
A JWT has three dot-separated parts: a header, a payload, and a signature. The header and payload are Base64URL-encoded JSON, and the signature proves the token was issued by your server and has not been tampered with.
What is a JWT?
A JWT is an open standard (RFC 7519) for creating tokens that carry claims — statements about an entity, usually the user, and additional metadata. The token is signed, so the party that receives it can verify both who issued it and that its contents were not altered in transit.
What makes JWTs popular is that they are self-contained: all the information the server needs to identify the user — user ID, roles, expiration — lives inside the token itself. The server does not need to keep a session store in memory or a database to look up who is asking, which makes JWTs a natural fit for microservices and horizontally scaled APIs.
JWT structure
A JWT looks like three long strings joined by dots: header.payload.signature. The header declares the token type and the signing algorithm, for example HS256 (HMAC with SHA-256). The payload holds the claims — registered ones like sub (subject), iat (issued at), and exp (expiration), plus any custom claims your app needs like roles or permissions.
The third part is the signature, computed by taking the encoded header and payload, joining them with a dot, and running the result through the algorithm named in the header using your secret key (or a private key for RSA/ECDSA). The server recomputes this on every request — if an attacker changes the payload, the signature will no longer match and the token is rejected. Note that the header and payload are only Base64URL encoded, not encrypted: anyone can read them.
How JWTs work for authentication
The typical flow starts with login: the client sends credentials to the server, which verifies them and mints a JWT containing the user's ID and an expiration time. The token is signed with the server's secret and returned to the client, which stores it (usually in localStorage, sessionStorage, or a cookie).
On every subsequent request, the client sends the token in the Authorization header as a Bearer token. The server validates the signature, checks the expiration, and — if everything passes — trusts the claims inside and serves the request. No database lookup is required, which is exactly why JWTs scale well across many stateless application servers.
When to use JWTs vs sessions
Traditional sessions store a small random session ID on the client and keep the actual session data server-side, usually in memory or a database. Sessions are easy to invalidate — just delete the server-side record — and they only ever expose an opaque identifier to the client.
JWTs trade that control for statelessness and size. Use JWTs when you need to share identity across multiple services, your infrastructure is distributed, or you want to avoid sticky sessions and shared session stores. Prefer traditional sessions when you need to revoke access immediately, when the claims are likely to change, or when you want the smallest possible client footprint. Many systems use short-lived JWTs plus a revocation list to get the best of both.
JWT security best practices
Always validate the signature before trusting any claim, and pin the algorithm in your verification code — a classic attack swaps the header's algorithm to "none" so the signature check is skipped entirely. Keep tokens short-lived (minutes, not days) and use refresh tokens to issue new access tokens without asking for credentials again.
Never store sensitive data in the payload, since it is only encoded, not encrypted — anyone who intercepts the token can read it. Use HTTPS everywhere so tokens cannot be sniffed in transit, and be careful where you store them client-side: an HttpOnly cookie protects against XSS-based theft, while localStorage is vulnerable to any script that runs on your page. Finally, use a strong secret key and rotate it if you suspect it has leaked.