ToolSite
All posts

What Is a JWT and How Does Token-Based Auth Work?

Learn how JSON Web Tokens (JWT) power stateless authentication. Understand access tokens, refresh tokens, and the full auth flow with our free JWT decoder.

By ToolSite6 min readguides

Token-Based Auth in One Sentence

Instead of sending a username and password with every request, the client sends a signed token that the server can verify without a database lookup.

A JSON Web Token (JWT, pronounced "jot") is the most common format for these tokens. It's compact, URL-safe, and carries claims about the user in a format the server trusts because the token is cryptographically signed.

The Auth Flow

  1. Login: the user sends credentials (email + password) to the server via a POST to /auth/login or similar.
  2. Verification: the server checks credentials against its database. If valid, it generates two tokens:
    • An access token (short-lived, usually 15 minutes to 1 hour)
    • A refresh token (long-lived, usually days to weeks)
  3. Access: the client stores the access token (in memory for SPAs, in an HTTP-only cookie for traditional web apps) and attaches it to every API request, typically in the Authorization: Bearer <token> header.
  4. Verification per request: the server validates the token's signature on every request. No database lookup needed. If the signature is valid and the token hasn't expired, the request is authorized.
  5. Refresh: when the access token expires, the client sends the refresh token to get a new access token without re-entering credentials.

Here's what a typical request looks like with a JWT:

GET /api/users/profile HTTP/1.1
Host: api.example.com
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...

The server extracts the token from the header, verifies the signature, checks the expiration, and either proceeds with the request or returns a 401.

The Token Itself

A JWT is three Base64url-encoded segments separated by dots:

header.payload.signature
  • Header: the signing algorithm (HS256, RS256) and token type (JWT). Example decoded: {"alg": "HS256", "typ": "JWT"}.
  • Payload: claims. Standard claims include sub (user ID), iat (issued at), exp (expiration). Custom claims can include role, email, or application-specific data.
  • Signature: a cryptographic hash of the header and payload, signed with the server's secret key (HMAC) or private key (RSA/ECDSA).

You can decode the header and payload with any Base64url decoder. The signature is not meant to be decoded. It's verified mathematically.

Example decoded payload:

{
  "sub": "1234567890",
  "name": "Alice",
  "iat": 1754000000,
  "exp": 1754003600,
  "role": "admin"
}

Why Stateless Auth Matters

Traditional session-based auth stores a session ID in a cookie and looks up the session in a database on every request. This works fine for a single server.

Token-based auth scales horizontally. Any server instance that knows the signing secret can verify the token. No shared session store. No database lookup. This is why JWTs are the default for microservices, distributed systems, and APIs consumed by mobile apps.

Consider a system with 10 API servers behind a load balancer. With session auth, every server needs access to a shared Redis or database to validate sessions. With JWT auth, each server validates tokens independently using the shared secret. The load balancer doesn't need sticky sessions. Any server can handle any request.

Access Token vs Refresh Token

The split exists for security:

  • Access token: short-lived. If stolen, the window of abuse is limited to 15 minutes. Sent with every request.
  • Refresh token: long-lived. Stored more securely. Sent only to the token endpoint to get a new access token. Can be revoked server-side.

This pattern means a compromised access token expires quickly and a stolen refresh token can be invalidated by the server.

A common implementation stores refresh tokens in an HTTP-only, Secure, SameSite cookie. The access token lives in memory (JavaScript variable in a SPA). The refresh endpoint reads the cookie, verifies the refresh token against a database or allowlist, and issues a new access token. If a refresh token is compromised, revoking it in the database prevents further token issuance.

Signing Algorithms: HMAC vs RSA

HS256 (HMAC with SHA-256): a single secret key is used to both sign and verify tokens. This is the simplest approach. Every service that needs to verify tokens must know the secret. If the secret leaks, anyone can forge tokens.

RS256 (RSA with SHA-256): a private key signs tokens, and a public key verifies them. The private key stays on the auth server. Other services only need the public key. If a public key leaks, no tokens can be forged. This is the preferred approach for multi-service architectures.

Most production systems use RS256 or ES256 (ECDSA). HS256 is fine for single- service applications or prototypes.

Common Pitfalls

  • Storing JWTs in localStorage: any JavaScript on the page can read localStorage, including third-party scripts from npm packages and browser extensions. XSS attacks steal tokens. Store access tokens in memory or HTTP-only cookies.
  • Not setting expiration: a token that never expires is a permanent credential. Always set exp.
  • Putting secrets in the payload: the payload is base64url-encoded, not encrypted. Anyone who captures the token can read it. Never put passwords, API keys, or PII in the payload.
  • Skipping signature verification: the cardinal JWT sin. Always verify the signature before trusting any claim in the payload. A JWT library that doesn't verify by default is a bug, not a feature. Always check your library's verify method is being called.
  • Using "none" algorithm: some JWT libraries accept alg: "none" in the header, meaning "unsigned token." An attacker can craft a token with any payload and alg: "none". If the server doesn't reject it, the attacker is authenticated as anyone. Always configure your JWT library to reject the "none" algorithm.

JWTs vs Opaque Tokens

JWTs carry claims in the token itself. The server reads the payload without a database call. Opaque tokens are random strings that must be looked up in a database or cache to retrieve the associated user data.

When to use which:

  • JWTs: high-traffic APIs where database lookups per request are expensive. Microservices where downstream services need user context without calling the auth service.
  • Opaque tokens: when you need to revoke individual tokens instantly (JWTs can't be revoked until they expire, unless you maintain a blocklist). When token payloads contain sensitive data you don't want exposed in the token itself.

Try it yourself: open the JWT Decoder, paste an example token, and inspect the decoded header and payload. You'll see the algorithm, user ID, and timestamps in plain text. The tool does not verify the signature. It shows you what anyone can see by decoding the first two segments.

Related Reading