Understanding JWT Tokens: A Developer's Complete Guide

JSON Web Tokens (JWTs) are everywhere — API authentication, single sign-on, microservices communication. If you build web applications, you'll work with them. This guide covers the structure, how they work, and the security practices that actually matter.

What Is a JWT?

A JWT is a compact, URL-safe token format defined by RFC 7519. It contains a JSON payload with claims (assertions about a user or entity) and is digitally signed so the receiver can verify it hasn't been tampered with.

A JWT looks like this:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

Three parts separated by dots: Header, Payload, and Signature.

The Three Parts

1. Header

The header specifies the signing algorithm and token type. It's Base64url-encoded JSON:

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

Common algorithms: HS256 (HMAC + SHA-256, symmetric), RS256 (RSA + SHA-256, asymmetric), ES256 (ECDSA + SHA-256, asymmetric).

2. Payload

The payload contains claims — key-value pairs with information about the user and token metadata:

{
  "sub": "user-12345",
  "name": "Jane Developer",
  "email": "jane@example.com",
  "role": "admin",
  "iat": 1711234567,
  "exp": 1711238167
}

Registered claims (standard fields):

  • sub — Subject (user ID)
  • iat — Issued at (Unix timestamp)
  • exp — Expiration time (Unix timestamp)
  • iss — Issuer (who created the token)
  • aud — Audience (who the token is for)
  • nbf — Not before (token not valid before this time)
  • jti — JWT ID (unique identifier for the token)

You can add any custom claims you need (role, permissions, org_id, etc.).

⚠️ Important: The payload is Base64url-encoded, not encrypted. Anyone with the token can read its contents. Never put sensitive data (passwords, credit card numbers, secrets) in a JWT payload.

3. Signature

The signature is created by hashing the encoded header and payload with a secret key:

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

The signature lets the server verify that the token hasn't been modified. If someone changes a claim in the payload, the signature won't match and the token is rejected.

How JWT Authentication Works

  1. User logs in — Client sends credentials (username + password) to the server.
  2. Server creates JWT — After verifying credentials, the server creates a JWT with user claims, signs it, and returns it.
  3. Client stores JWT — The token is stored (usually in memory or an httpOnly cookie).
  4. Client sends JWT with requests — Each API request includes the token in the Authorization: Bearer <token> header.
  5. Server validates JWT — The server verifies the signature and checks expiration before processing the request.

HS256 vs RS256: Symmetric vs Asymmetric

HS256 (HMAC) uses a single shared secret for both signing and verification. Simple, fast, but every service that verifies tokens needs the secret — a security risk in distributed systems.

RS256 (RSA) uses a private key to sign and a public key to verify. Only the auth server needs the private key; all other services use the public key. Ideal for microservices architectures.

HS256: auth-server signs with SECRET → api-server verifies with SECRET
RS256: auth-server signs with PRIVATE_KEY → api-server verifies with PUBLIC_KEY

Security Best Practices

Token Storage

  • Best: httpOnly, Secure, SameSite cookies — immune to XSS attacks
  • Acceptable: In-memory (JavaScript variable) — lost on refresh but safe from XSS persistence
  • Avoid: localStorage or sessionStorage — accessible to any JavaScript on the page, including XSS payloads

Always Validate These

  • Signature — Never trust an unverified token
  • exp claim — Reject expired tokens
  • iss claim — Confirm the token came from your auth server
  • aud claim — Confirm the token is meant for your service
  • alg header — Reject "alg": "none" (a classic attack vector)

Token Expiration

Use short-lived access tokens (5–15 minutes) paired with longer-lived refresh tokens. This limits the damage window if a token is stolen.

Access token:  exp = 15 minutes
Refresh token: exp = 7 days (stored in httpOnly cookie, rotated on use)

Token Revocation

JWTs are stateless — the server doesn't track them. To revoke a token before expiration, you need a blocklist (database or Redis set of revoked jti values). Check the blocklist on every request. This adds a DB lookup but is necessary for logout, password changes, and compromised accounts.

Common Mistakes

  • Putting secrets in the payload — The payload is readable by anyone.
  • Using "alg": "none" — Some libraries accept unsigned tokens if the algorithm is set to none. Always validate the algorithm server-side.
  • Not checking expiration — Tokens without exp never expire. Always set and enforce expiration.
  • Long-lived tokens with no rotation — A stolen token that's valid for months is a serious risk.
  • Storing tokens in localStorage — Vulnerable to XSS. Use httpOnly cookies instead.

When NOT to Use JWTs

  • Session management for a monolith — Server-side sessions with a session ID cookie are simpler and easier to revoke.
  • Storing large amounts of data — JWTs are sent with every request. Keep payloads small (under 1 KB).
  • When you need instant revocation — If immediate logout is critical, server-side sessions are more straightforward.

Try It Yourself

Paste any JWT into the JWT Decoder tool to instantly see its header, payload, and expiration — all processed in your browser, no data sent anywhere.

Want to inspect a token?

Open JWT Decoder →