AlgoInsight

System Design

What Are JSON Web Tokens (JWTs)?

JWTs are how modern applications carry proof of who you are without the server having to remember you. Three dot-separated parts, one elegant idea - and a few sharp edges. Here's how they actually work and when to use them.

By Admin2026-06-1814 min readjwtauthenticationsecuritytokenssystem-designapi

When you log into a modern web app and then make request after request without re-entering your password each time, something is carrying proof of who you are with each request. Increasingly, that something is a JSON Web Token (JWT) - a compact, self-contained token that holds your identity and can be verified by the server without the server having to remember you at all. That last part is the elegant trick: JWTs enable stateless authentication. This guide explains what a JWT is, its structure, how it works, and the sharp edges to know about.

What a JWT is

A JWT is a compact, self-contained token for securely transmitting information (claims) between parties, most commonly used for authentication and authorization. "Self-contained" is the key word: the token itself carries the information (who the user is, what they can do, when it expires), and it's signed so the receiver can verify it hasn't been tampered with. The server doesn't need to look anything up to trust a valid JWT - the token carries its own proof.

This is what makes JWTs popular for stateless authentication: instead of the server keeping session data and looking you up on every request (stateful), it issues you a signed token that contains your identity, and on each request it just verifies the token's signature. No server-side session storage, no database lookup to check who you are - the token proves it. That statelessness is a big deal for scaling, which we'll come back to, and it's the single property that explains why JWTs rose alongside microservices and distributed APIs.

The structure: three parts

A JWT is a string with three parts separated by dots: header.payload.signature. Each part is Base64URL-encoded (a way of representing data as a URL-safe string - note this is encoding, not encryption; we'll return to that). The three parts:

1. Header. Metadata about the token - primarily the algorithm used to sign it (so the receiver knows how to verify the signature) and the token type. Small and simple.

2. Payload. The claims - the actual information the token carries. This includes things like the user's ID, perhaps their roles or permissions, and standard fields like an expiration time (exp) saying when the token is no longer valid, who issued it, and when. The payload is the meat of the token: the data being transmitted.

3. Signature. The cryptographic signature that makes the token trustworthy. It's created by signing the header and payload together with a key, so that any change to the header or payload would invalidate the signature. This is what lets the receiver verify the token is authentic and untampered.

graph LR
    JWT["JWT string"] --> H["Header<br/>(algorithm, type)"]
    JWT --> P["Payload<br/>(claims: user id, exp, roles)"]
    JWT --> S["Signature<br/>(signs header + payload)"]
    H -.->|verifies| S
    P -.->|verifies| S
This diagram shows a JWT's three parts. The JWT string is divided into a header, a payload, and a signature, separated by dots. The header contains metadata like the signing algorithm and token type. The payload contains the claims - the user's ID, an expiration time, roles, and other information. The signature is created by signing the header and payload together, so any tampering with either invalidates it. The signature is what lets the receiver verify the header and payload are authentic and unaltered.

How the signature creates trust

The signature is the clever part. When the server issues a JWT, it creates the signature by combining the header and payload and signing them with a key (a secret known only to the server, or a private key). When the server later receives the token back, it recomputes the signature from the header and payload using the same key and checks that it matches the signature in the token.

If they match, the server knows two things: the token was issued by it (only it has the key to produce a valid signature), and the header and payload haven't been altered (any change would produce a different signature, failing the check). If someone tampered with the payload - say, changed the user ID to impersonate someone, or extended the expiration - the signature wouldn't match, and the server would reject the token.

This is why the server doesn't need to store anything: the signature, verifiable with the server's key, is the proof that the token is genuine and unmodified. The token carries its own trustworthiness.

The authentication flow

Here's how JWTs work in a typical login flow:

  1. Login. You log in with your credentials. The server verifies them and creates a JWT containing your identity (and an expiration), signs it, and sends it back to you.
  2. Store. Your client (browser/app) stores the token.
  3. Request. On each subsequent request, your client sends the JWT (typically in an HTTP header).
  4. Verify. The server verifies the token's signature (using its key) and, if valid and not expired, trusts the identity in the payload - granting access. No database lookup, no session store; just signature verification.

So after login, every request carries its own proof of identity in the token, and the server validates it statelessly. This is the core appeal: authentication without server-side session state.

Why stateless matters: the scaling advantage

The stateless property is JWT's headline benefit, and it matters for scaling. With traditional server-side sessions, the server stores session data and looks you up on each request - which means either all requests must go to the server holding your session (sticky sessions, limiting load balancing) or all servers must share a session store (added complexity). With JWTs, any server can verify any token using the shared key, with no session lookup. So you can scale out to many servers behind a load balancer, and any of them can handle any request, because the token is self-contained.

This fits naturally with stateless, horizontally-scalable architectures, microservices, and APIs - which is a big reason JWTs became popular. The token travels with the request and proves identity anywhere, so the auth layer doesn't constrain how you distribute load. (See the stateful vs stateless guide.)

JWT vs. traditional session cookies

To really understand JWTs, compare them to the traditional alternative: server-side sessions with cookies.

In the traditional model, when you log in, the server creates a session (storing your data server-side) and gives you a session ID in a cookie. On each request, you send the cookie, and the server looks up your session by that ID. The identity lives on the server; the cookie is just a key to find it.

With JWTs, the identity lives in the token itself (carried by you), verified by signature rather than lookup. The contrast:

Server-side sessions JWTs
Where state lives On the server (session store) In the token (client holds it)
Verification Look up session by ID Verify signature, no lookup
Scaling Needs shared session store or sticky sessions Any server verifies any token
Revocation Easy (delete the session) Hard (token valid until expiry)
Size Tiny cookie (just an ID) Larger token (carries claims)

Neither is universally better - it's a trade-off. Sessions make revocation trivial but require shared state for scaling; JWTs scale statelessly but make revocation hard. The right choice depends on whether easy revocation or stateless scaling matters more, and many systems combine them. Understanding this contrast is understanding why you'd reach for a JWT in the first place - and when a plain session might serve better.

Where to store a JWT (and the security stakes)

Once the client has a JWT, where does it keep it? The common options each have trade-offs:

The key point: a JWT is a bearer token - whoever holds it can use it. So protecting it in transit (always over HTTPS) and at rest (storing it safely) is essential, because a stolen token grants access until it expires. This is why the storage choice matters and why short expirations limit the damage of theft. The token's power - carrying its own proof of identity - is also its risk: it must be guarded like the credential it is.

Signed vs. encrypted: JWS and JWE

A clarification that resolves a common confusion: the standard JWT described here is signed (technically a JWS - JSON Web Signature). Signing makes it tamper-evident (you can verify it wasn't altered) but not private (the payload is readable). There's also an encrypted variant (JWE - JSON Web Encryption) where the payload is encrypted and unreadable without the key.

In practice, most JWTs you encounter are signed-but-readable (JWS), which is why the "don't put secrets in the payload" rule matters. If you genuinely need the contents hidden, encryption (JWE) is the tool - but it's less common; the usual pattern is to keep non-sensitive claims in a signed JWT and not put anything secret in it. Knowing that "JWT" usually means "signed and readable, not encrypted" is exactly the distinction that prevents the dangerous mistake of treating a standard JWT's payload as private.

Common JWT mistakes

Because JWTs are easy to use but easy to misuse, a few mistakes recur:

The theme: JWTs are powerful but unforgiving of misuse - and the safe path is short expirations, no secrets in the payload, strong guarded keys, a well-tested library, and using them where their statelessness actually earns its keep.

The sharp edges

JWTs are powerful but have real gotchas that you must know to use them safely:

These aren't reasons to avoid JWTs - they're the conditions for using them correctly: short expirations, no sensitive data in the payload, a well-protected key, and a plan for revocation if needed.

Interview: JWTs come up in authentication and system design discussions. The strong answer covers the structure (header, payload, signature), how the signature enables stateless verification (the server checks the signature with its key, no lookup needed), and the scaling benefit (any server can verify any token, fitting horizontal scaling). The senior signal is naming the trade-offs: the payload is readable so no secrets go in it, and revocation is hard so tokens should be short-lived. Showing you know both the appeal (stateless, scalable) and the sharp edges (readable, hard to revoke) demonstrates real understanding.

The bottom line

A JWT is a compact, self-contained, signed token that carries identity claims, enabling stateless authentication. It has three dot-separated parts: a header (metadata and the signing algorithm), a payload (the claims - user ID, expiration, roles), and a signature (which signs the header and payload so tampering is detectable). On login the server issues a signed token; on each request the client sends it and the server verifies the signature with its key - no session lookup, so any server can validate any token, which is the scaling advantage. The sharp edges: the payload is readable (not encrypted), so no secrets in it; revocation is hard, so keep tokens short-lived; and the signing key must be guarded.

Used with those conditions in mind, JWTs are an elegant way to carry verifiable identity without server-side state. The mental model to keep: a JWT is a signed claim you carry with you - the server stamps it so it can't be forged or altered, hands it to you, and trusts it back because of the stamp, not because it remembers issuing it. That single shift, from "the server remembers who you are" to "you carry tamper-proof evidence of who you are," is what makes stateless authentication work and what makes JWTs scale - and also what creates every one of their sharp edges. Hold that model, respect the conditions, and you'll use JWTs well. We cover authentication and stateless design across System Design Fundamentals.


Go deeper

More articles