Conceptly
← All Concepts
πŸ”‘

JSON Web Token

SecurityA self-contained token that identifies the requester without the server storing any state

JWT (JSON Web Token) is a token that contains user authentication information encoded as JSON and signed. Three parts -- Header, Payload, and Signature -- are Base64-encoded and joined by dots (.) into a single string. By verifying only the signature of this token, the server can determine who sent the request and what permissions they hold without looking up a separate session store. The token carries claims such as expiry time (exp), issuer (iss), and subject (sub), so a single token conveys both authentication and authorization information. The signing algorithm can be either HMAC (symmetric key) or RSA/ECDSA (asymmetric key); using an asymmetric key allows the issuer and the verifier to be separate. Because a JWT once issued is difficult to invalidate unilaterally from the server, the common practice is to set a short expiry time and operate a separate Refresh Token.

β–ΆArchitecture Diagram

πŸ“Š Data Flow

Dashed line animations indicate the flow direction of data or requests

Why do you need it?

When a user sends a request after logging in, the server must again determine whether this request comes from the person who just logged in. The traditional approach was for the server to issue a session ID and store the corresponding user information in memory or a database. This works fine with a single server. But as the system scales to multiple servers, the question of where to keep the session becomes a problem. Having all servers share the same session store requires separate infrastructure, and that store can become a bottleneck or a single point of failure. There was a need for a way to confirm 'who is this requester and what permissions do they have' without a session lookup, and the answer was to embed that information in a signed token for the client to carry.

Why did this approach emerge?

Server session-based authentication was the standard when web applications ran on a single server. A user logged in, the server created a session in memory, and a session ID cookie was sent to the browser. But as services scaled up, multiple servers appeared behind load balancers, and in microservice architectures a single request might traverse several services. In that environment, the idea of 'embedding authentication state in the token itself so it can be verified anywhere without consulting a central store' gained traction. JWT, standardized in RFC 7519 in 2015, emerged to solve this problem of propagating authentication in distributed environments, and was rapidly adopted as the token format for OAuth 2.0 and OpenID Connect.

How does it work inside?

JWT consists of three segments separated by dots. The first segment, the Header, contains the signing algorithm (e.g., HS256, RS256) and token type. The second, the Payload, carries claims encoded as JSON -- user identifier, expiry time, issue time, permission scopes, and others. The third, the Signature, is the result of signing the Header and Payload with a secret key. When the server receives the token, it re-signs the Header and Payload with the same algorithm and compares the result against the Signature. If they match, the content has not been tampered with, and the only additional check needed is whether the expiry time in the Payload is still valid. No database query is involved in this process. An important caveat is that the Payload is Base64-encoded, not encrypted. Anyone can decode it and read its contents, so passwords and personal information must never be placed in the Payload. JWT does not 'prevent reading' -- it 'prevents modification'.

In Code

Reading the token in three parts

const [headerB64, payloadB64, signatureB64] = token.split(".");
const payload = JSON.parse(
  atob(payloadB64.replace(/-/g, "+").replace(/_/g, "/"))
);

Reading the split by `.` and the Payload decode makes it clear that a JWT is a structured token, not just an opaque string.

Verifying by comparing the signature

import { createHmac } from "node:crypto";

const signed = `${headerB64}.${payloadB64}`;
const expected = createHmac("sha256", secret).update(signed).digest("base64url");

if (expected !== signatureB64) throw new Error("invalid token");

The signature is there to prevent tampering, not to hide the Payload. Reading the comparison against `signatureB64` shows the job it actually does.

What is it often confused with?

JWT and server sessions both solve the same problem of identifying authenticated users in subsequent HTTP requests. The difference is where the state lives. A server session stores authentication state on the server and gives the client only a key (the Session ID). JWT has the client carry the authentication information itself in the token, and the server only verifies the signature. Server sessions can immediately invalidate a specific user's session by deleting it from the store. Forced logout by an administrator and limiting concurrent logins are natural. The tradeoff is that the server must maintain a session store, and sharing that store across multiple servers creates complexity. JWT requires no server-side state, making horizontal scaling simple. But once issued, a token is difficult to revoke unilaterally before it expires. To force logout from the server side requires maintaining a separate blacklist, at which point state appears on the server and the advantage of JWT is diluted. JWT is not a silver bullet. A stolen token can be abused until it expires, and putting too much information in the Payload means carrying a large token on every request. If immediate revocation is required, a blacklist store eventually becomes necessary, partly giving up the statelessness advantage.

When should you use it?

JWT fits most naturally in environments with multiple server instances where a shared session store is difficult to maintain. Regardless of which server the load balancer routes a request to, only the token signature needs to be verified, eliminating the need for workarounds like sticky sessions. JWT is also frequently used for authentication propagation between microservices. After the API Gateway verifies the JWT, internal services can read the same token's claims to make their own authorization decisions independently. In production, token lifetime management deserves attention. The common pattern is to set a short expiry on the access token -- within 15 minutes -- and operate a separate Refresh Token to renew expired access tokens. The Refresh Token is stored and managed on the server, so it can be revoked when necessary. Where to store the JWT in the browser is also a design decision. Putting it in localStorage exposes it to XSS attacks; putting it in an httpOnly cookie requires a separate defense against CSRF. The storage location should be chosen based on the environment and threat model.

Stateless authentication -- verifying users with only a token signature, without the server storing any sessionCross-microservice authentication propagation -- verifying in service B a token received from service A without a separate lookupOAuth 2.0 access token -- delivering permission information issued by an authorization server in JWT formatSingle Sign-On -- sharing authentication information from a single login across multiple services via token