October 4, 2026 · Yunus Emre Vurgun

How Does JWT Authentication Actually Work?

auth · api · web · tutorial

JWT authentication works by handing the client a signed token after login; the client sends that token on every request, and the server verifies the signature to know who the user is — no server-side session lookup required. A JWT (JSON Web Token) has three Base64URL-encoded parts — header, payload, and signature — joined by dots into a single string. Because verification only needs the signing secret or public key, JWTs scale well across services, but they cannot be easily revoked before they expire.

The anatomy of a JWT: header, payload, signature

A JWT looks like three random strings separated by dots, for example eyJhbGciOi....eyJzdWIiOi....SflKxwRJ.... Each segment is Base64URL-encoded JSON, and anyone can decode the first two — a JWT is signed, not encrypted. The header names the signing algorithm and token type: {"alg": "HS256", "typ": "JWT"}. The payload carries claims, which are statements about the user and the token itself, such as sub (subject, usually the user id), iat (issued at), exp (expires at), and any application data like roles.

The signature is what makes the token trustworthy. The server takes the encoded header, a dot, and the encoded payload, then signs that string with its secret (HMAC) or private key (RSA/ECDSA). When a request arrives, the server recomputes the signature and compares; if even one character of the payload was altered, the signatures will not match and the token is rejected. This is why you must never put passwords, API secrets, or personal data you would not show the user into a payload — it is readable by design. Tokens are one of several ways to prove identity, surveyed alongside passwords, OTPs, and biometrics in the authentication factors reference.

How a JWT login flow works step by step

The complete flow has four steps. First, the user submits credentials — typically a username and password — to your login endpoint over HTTPS. Second, the server validates those credentials against its user store, then mints two tokens: a short-lived access token (minutes) and a longer-lived refresh token (days or weeks). Third, the client stores both tokens and attaches the access token to subsequent API calls in the Authorization: Bearer <token> header. Fourth, on each request the API verifies the signature, checks the expiry, and reads the user id from the sub claim.

When the access token expires, the client trades the refresh token for a fresh pair at a dedicated endpoint, without asking the user to log in again. This split is deliberate: access tokens circulate constantly and are exposed on every request, so keeping them short-lived limits the damage if one leaks. Refresh tokens are used rarely, can be rotated on every use, and can be revoked server-side by keeping a small allow-list or version counter. Decoding a token to inspect its claims takes one command:

# Decode the payload (middle segment) of a JWT
echo "eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4ifQ" | base64 -d 2>/dev/null; echo

On the server, verification means checking four things in order: the signature is valid, the exp claim is in the future, the iss and aud claims match your service when you use them, and the algorithm in the header is one you expect. That last check matters: explicitly allow-list algorithms like RS256 and reject none, or an attacker can forge an unsigned token your code accepts.

JWTs vs sessions: when to use each

The classic alternative to JWTs is the server-side session: the server stores session data in memory or Redis and hands the client an opaque session id in a cookie. Neither approach wins everywhere, so pick by architecture:

ConcernJWT (stateless)Session (stateful)
ScalabilityAny server can verify with the key; nothing shared.Needs shared store (Redis) or sticky sessions.
RevocationHard — token stays valid until expiry unless denylisted.Easy — delete the session row and the user is out.
Payload sizeGrows with claims; sent on every request.Tiny cookie; data stays server-side.
Cross-domain / mobileNatural fit for APIs, SPAs, and mobile apps.Cookies are awkward across domains and native apps.
Security surfaceKey management and storage pitfalls.Session fixation and store availability.

A good rule of thumb: use sessions for a traditional server-rendered app on one domain, and JWTs for APIs consumed by SPAs, mobile apps, or multiple microservices. Many systems combine both — a session cookie for the web app and JWT access tokens for the API. And remember that not every API needs user identity at all; for public reference data, skipping auth entirely can be the right call, as argued in why we serve data without authentication.

Should I store my JWT in localStorage?

This is the most-asked JWT question, and the honest answer is that every storage option trades one risk for another. localStorage is vulnerable to cross-site scripting (XSS): any injected script can read your tokens and send them to an attacker. An HttpOnly cookie cannot be read by JavaScript, which closes that hole, but cookies are sent automatically and therefore need CSRF protection via SameSite attributes and anti-CSRF tokens.

For most browser apps, the current best practice is a split: keep the short-lived access token in memory (a JavaScript variable, lost on reload but invisible to both XSS persistence and CSRF), and keep the refresh token in a strict HttpOnly, Secure, SameSite=Lax cookie that only the refresh endpoint reads. On page load, the app silently calls the refresh endpoint to get a new access token. Mobile and native apps should use the platform keychain or keystore rather than either browser mechanism. Whichever you choose, the fundamentals still apply: short expiries, HTTPS everywhere, and minimal claims in the payload.

JWT security checklist

Before shipping JWT authentication, walk through this checklist. Use a strong algorithm — RS256 or ES256 for multi-service setups so each service only holds the public key — and reject unexpected algorithms explicitly. Keep access tokens short-lived, around five to fifteen minutes, and rotate refresh tokens on every use. Validate exp, iss, and aud on every request, and never trust claims from a token whose signature you did not verify.

Protect your signing keys like production database credentials: load them from a secret manager, never commit them, and have a rotation plan with overlapping key ids (kid) so you can roll keys without downtime. Log token validation failures for anomaly detection, and give users a way to sign out everywhere by bumping a per-user token version that you check as a claim. For how the Authorization header carrying your tokens fits into the wider HTTP auth landscape, see the HTTP authentication methods dataset.