JWT Authentication Explained: Complete Guide for Developers

What Is a JWT?

A **JSON Web Token (JWT)** is a compact, URL-safe string that carries claims between two parties. After a user logs in, the server issues a JWT. The client sends it back with each request, usually in the `Authorization` header, and the server verifies it without a database lookup.

A JWT looks like this:


xxxxx.yyyyy.zzzzz

Three Base64URL-encoded parts separated by dots: **header**, **payload**, and **signature**. The signature is what makes the token trustworthy. Paste any token into the [JWT decoder](/en/jwt-decoder) to inspect its header and payload instantly.

The Three Parts of a JWT

Take this example token (truncated):


eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
.eyJzdWIiOiIxMjM0IiwibmFtZSI6IkFsaWNlIiwiZXhwIjoxNzU0MTIzNjAwfQ
.sFlKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

1. Header

The header describes the token type and the signing algorithm.


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

2. Payload

The payload contains **claims**: statements about the subject and the token itself.


{
  "sub": "1234",
  "name": "Alice",
  "role": "admin",
  "iat": 1754000000,
  "exp": 1754123600
}

Common registered claims:

  • `sub`: subject (usually the user id).
  • `iss`: issuer.
  • `aud`: intended audience.
  • `exp`: expiration time.
  • `nbf`: not-before time.
  • `iat`: issued-at time.

The payload is **Base64URL-encoded, not encrypted**. Anyone who reads the token can read the payload. Never put secrets, passwords, or sensitive personal data in a JWT unless you are using JWE (encrypted tokens).

3. Signature

The signature proves the token has not been tampered with. For HMAC-SHA256:


HMACSHA256(
  base64url(header) + "." + base64url(payload),
  secret
)

The server recomputes the signature with its secret (or public key for asymmetric algorithms) and rejects the token if it does not match.

How JWT Authentication Works

A typical flow:

1. **Login.** The user submits credentials. The server verifies them against the database.

2. **Issue.** The server creates an access token (and usually a refresh token), signs it, and returns it to the client.

3. **Store.** The client stores the token. For browser apps the safest store is an **httpOnly, Secure, SameSite cookie**.

4. **Send.** On each request, the client sends the token:


Authorization: Bearer eyJhbGciOi...

5. **Verify.** The server checks the signature, the `exp`, the `iss` and `aud`, and any custom claims, then authorizes the request. No database lookup is needed for verification.

6. **Refresh.** When the access token expires, the client uses the refresh token to get a new access token without asking the user to log in again.

Access Tokens vs Refresh Tokens

PurposeAuthorize one API requestObtain new access tokens
LifetimeShort: 5 to 15 minutesLong: days to weeks
StorageMemory or httpOnly cookiehttpOnly cookie, secure storage on mobile
Sent toEvery API endpointOnly the token endpoint
RevocationHard; rely on expiryShould be server-side revocable

The pattern keeps access tokens short-lived, so a stolen access token is useful for only minutes. The refresh token lives longer but only travels to one endpoint, so its attack surface is smaller.

Security Best Practices

JWTs are safe when used correctly and dangerous when used carelessly. Follow these rules.

**1. Keep access tokens short-lived.** Five to fifteen minutes is typical. The shorter the better; you cannot easily revoke a JWT without extra infrastructure.

**2. Do not store tokens in localStorage.** Any script on the page, including third-party scripts and compromised dependencies, can read `localStorage`. Prefer an httpOnly, Secure, SameSite=Lax (or Strict) cookie. On mobile, use the platform's secure storage (Keychain, Keystore).

**3. Always validate the signature and algorithm.** Use a battle-tested library, never hand-rolled code. Verify `alg`, `exp`, `nbf`, `iss`, `aud`, and any claims you depend on. Do not trust the `alg` from the header; pin the expected algorithm.

**4. Use strong secrets and asymmetric signing.** For HMAC, use a long, random, rotated secret. For distributed systems, prefer **RS256** or **ES256** so services verify with a public key and never hold the signing key.

**5. Rotate signing keys.** Support multiple valid keys at once (`kid` header) so you can rotate without invalidating every user.

**6. Revoke refresh tokens server-side.** Maintain a token allowlist or a revocation list, or use short-lived refresh tokens with a sliding session. Never treat a refresh token as irrevocable.

**7. Scope tokens narrowly.** Include only the claims you need. More claims mean more damage if the token leaks.

**8. Use HTTPS everywhere.** A JWT over HTTP is trivially intercepted.

**9. Set cookie flags.** `Secure`, `HttpOnly`, and `SameSite` prevent most browser-based attacks.

Common JWT Vulnerabilities

**The `alg: none` attack.** A historic flaw in some libraries let an attacker set `"alg":"none"` and remove the signature, and the library would accept the token. Modern libraries block this by default. Defend by pinning the algorithm on the server and rejecting `none`.

**Algorithm confusion.** If a server expects RS256 (asymmetric) but accepts HS256 using the public key as the HMAC secret, an attacker who knows the public key can forge tokens. Mitigate by pinning the algorithm and never mixing asymmetric and symmetric verification for the same key.

**Weak HMAC secrets.** A short or dictionary-word secret can be brute-forced offline. Generate secrets with at least 256 bits of entropy. The [password generator](/en/password-generator) can produce strong secrets; better, use a managed key store.

**Information disclosure in the payload.** Developers put passwords, SSNs, and API keys in the payload because it looks encrypted. It is not. Anyone with the token can decode it. Use the [JWT decoder](/en/jwt-decoder) on your own tokens to confirm what you are leaking.

**Token theft via XSS or logging.** Tokens in `localStorage` are stolen by XSS. Tokens accidentally logged by middleware or in URL query strings leak too. Keep them out of logs and URLs.

**Stolen refresh tokens.** Because refresh tokens live a long time, treat them as valuable. Bind them to device fingerprints, rotate them on use, and revoke on suspicious activity.

How to Decode and Debug a JWT

During development you constantly need to inspect tokens. The fastest path is to paste the token into the [JWT decoder](/en/jwt-decoder). It shows the header, the decoded payload, the expiration time in human form, and whether the token has already expired.

To decode in code, remember that the payload is Base64URL-encoded JSON. In JavaScript:


function decodePayload(token) {
  const part = token.split('.')[1];
  const json = atob(part.replace(/-/g, '+').replace(/_/g, '/'));
  return JSON.parse(json);
}

Decoding only reads the payload; it does **not** verify the signature. Verification must happen on the server (or any component that authorizes based on the token) using the signing key and a vetted library.

Common debugging checklist:

  • Is the `exp` in the past? The token is expired.
  • Is the `iss` and `aud` what your server expects?
  • Is the `alg` what you configured?
  • Does the signature verify with the current key (`kid`)?
  • Are the required claims present (`sub`, `role`, `scope`)?
  • Is the token base64url, not standard base64?

Stateless vs Stateful Sessions

JWTs are often sold as "stateless" authentication: the server verifies the signature and trusts the claims with no database hit. That is great for performance but makes revocation hard. Real-world systems are usually hybrid:

  • Access tokens are stateless and short-lived.
  • Refresh tokens are stateful and revocable.
  • A denylist of revoked access tokens (checked for the few minutes until they expire) covers urgent logouts.

Pick the model that fits your threat model. Pure stateless JWT is fine for low-risk APIs; high-security products add server-side checks.

JWT vs Sessions vs Opaque Tokens

Server stateYesNo (unless revocation list)Yes (lookup on each request)
Verifiable offlineNoYesNo
RevocationTrivialHardTrivial
Cross-serviceAwkwardEasyEasy with introspection
Payload visibilityHiddenVisible (unless JWE)Hidden

JWTs shine in distributed systems and APIs with many services. Opaque tokens are simpler and safer if you do not need cross-service verification.

When to Use JWT (and When Not To)

**Use JWT when:**

  • You have distributed services that need to verify identity independently.
  • You issue tokens to mobile or third-party clients.
  • You want stateless verification for performance.
  • You federate identity through OAuth 2.0 or OpenID Connect.

**Avoid JWT when:**

  • You need instant revocation and do not want a denylist.
  • The session is a single first-party browser app and cookies work fine.
  • You would put sensitive data in the payload because it feels encrypted.

TL;DR

A JWT is a signed, Base64URL-encoded container for claims. Sign it with a strong key or asymmetric pair, keep access tokens short, store them in httpOnly cookies (not localStorage), validate everything on the server, and never trust the header's `alg`. Decode and inspect your tokens with the [JWT decoder](/en/jwt-decoder), and if you need to transport other binary payloads safely, the [Base64 tool](/en/base64) handles encoding and decoding in the same place.