JWT (JSON Web Token) is a compact format for transmitting signed data, most often information about an authenticated user. A token has three parts separated by dots: header.payload.signature. It's become the default choice across the Node/Express, Auth0, Firebase, and general SPA-plus-API stack that dominates English-language web development tutorials.
The three parts of a token
- Header — JSON containing the token type and signing algorithm (e.g. HS256 or RS256), Base64URL-encoded.
- Payload — JSON with "claims": user data, issue time, expiration, and so on, also Base64URL-encoded.
- Signature — a signature computed over the header and payload using a secret or private key, confirming the token hasn't been tampered with.
Where JWT shows up in a typical stack
A React or Vue single-page app calling an Express or Rails API is the textbook case: the backend issues a JWT on login, the frontend stores it (ideally not in localStorage, for the reasons below) and attaches it as a Bearer token on every request. Auth-as-a-service providers like Auth0, Clerk, or AWS Cognito lean on this same format, which is why pasting a token from your browser's dev tools into a decoder is one of the most common debugging steps in modern web development.
Base64URL, not plain Base64
JWT uses a URL-safe variant of Base64: the characters + and / are replaced with - and _, and the = padding is usually dropped. That lets you drop a token straight into a URL or header without extra encoding.
An important distinction: decoding ≠ verifying
The header and payload are just Base64URL — anyone can decode them and read the contents without any key. Decoding a token doesn't prove the data hasn't been tampered with. You can only trust a token's contents after verifying its signature with the matching key — and that verification happens on the server, not by a client that just looks at what's inside the token.
A dangerous attack: swapping the algorithm for "none"
The JWT spec allows the none algorithm — an unsigned token. If a backend naively trusts the alg field from the token's header instead of verifying the signature against a fixed, known-in-advance algorithm, an attacker can swap alg to none, strip the signature, and the token will pass verification with arbitrary data. Robust JWT libraries require the expected algorithm to be specified explicitly at verification time precisely to guard against this attack.
Why localStorage is a debated choice
A large share of English-language security discourse around JWT centers on where to store the token client-side. localStorage is readable by any JavaScript running on the page, so an XSS vulnerability anywhere in the app can exfiltrate it; an httpOnly cookie is invisible to JavaScript but reintroduces CSRF concerns. Neither option is universally "correct" — the right tradeoff depends on the app's threat model.