AlgoMaster Logo

JSON Web Tokens (JWT)

High Priority18 min readUpdated October 9, 2026
AI Mock Interview

Practice this topic in a realistic system design interview

Listen to this chapter
Unlock Audio

Premium Video

This video is available to premium subscribers only

Unlock Full Access

When you log into an application, the server needs a way to verify your identity on subsequent requests without asking you to log in again.

One common approach is to use a JSON Web Token, or JWT, which allows multiple services to verify who you are without maintaining a separate session for every user.

In this chapter, we will look at JWTs, how they work, how they are signed and verified, and the important trade-offs you should understand before using them.

Loading simulation...

1. Server-Side Sessions

A traditional approach is to use server-side sessions.

When a user logs in with an email and password, the server verifies the credentials, creates a session, and stores it in a database or an in-memory store like Redis. The client receives a session ID, usually in a cookie, and sends it with every request. The server then looks up the session, identifies the user, and processes the request.

POST /login (email, password)Create sessionSet-Cookie: session_id=abc123GET /orders (Cookie: session_id=abc123)Look up abc123user 12345Orders for user 12345ClientServerSession Store (Redis)
7 / 7
algomaster.io

This works well and is still one of the simplest options for many applications.

It becomes harder in a distributed system. A single request might pass through an API gateway, a user service, an order service, and a payment service. If authentication depends on a central session store, these services may need to repeatedly fetch session data or rely on another service to identify the user.

2. Why JWTs Are Useful

JWTs offer another approach. Instead of sending an opaque session ID, the server issues a token containing claims about the user.

Because the token is cryptographically signed, other services can verify that it came from a trusted issuer and has not been modified. This lets services verify the user's identity from the token itself, without a session lookup on every request. That is one of the main reasons JWTs are useful in distributed systems.

One important distinction is that JWT itself is not an authentication protocol. It is simply a token format. Protocols such as OAuth 2.0 and OpenID Connect can use JWTs, and they decide when tokens are issued, who receives them, and how refresh works. But JWTs can also be used independently.

Most JWTs used as access tokens are signed. A signed JWT is technically called a JWS (JSON Web Signature). Encrypted JWTs also exist, called JWE (JSON Web Encryption), but they are less common for API access tokens because they add more key-management work.

3. The Structure of a JWT

At a high level, a JWT is a compact token that lets one system send a set of claims to another system in a way that can be verified. A claim is simply a piece of information, such as a user ID, a role, or the token's expiration time.

A typical JWT looks like a long string with three sections separated by dots:

  • The header describes how the token is signed.
  • The payload contains the claims.
  • The signature lets the receiving system verify that the token has not been modified.

Let's look at each of these parts in more detail.

4. The Header

The first part of a JWT is the header. A typical header looks like this:

FieldMeaning
algThe algorithm used to sign the token. RS256 means RSA with SHA-256.
typThe token type, which is JWT
kidThe key ID, which tells the receiver which key to use to verify the signature

This JSON object is then Base64URL encoded to create the first part of the token.

The kid becomes especially useful when the authentication system rotates its signing keys over time. Old tokens may still be signed with last month's key while new tokens use the current one, and the kid tells the receiver which one to pick.

So the header does not contain information about the user. It describes how the token was signed and how its signature should be verified.

One caution: the API should not trust the header blindly. The header can help pick a key, but the server must decide which algorithms and keys it accepts. We come back to this in the security checklist.

5. The Payload

The second part of a JWT is the payload. The payload contains claims, which are pieces of information about the token and the subject it represents. A payload might look like this:

The first five are standard, registered claims. The table below also includes two optional ones you will often see:

ClaimMeaning
subSubject: the user or entity the token belongs to
issIssuer: who created the token
audAudience: which service or application the token is intended for
iatIssued at: when the token was created
expExpiration: when the token stops being valid
nbfNot before: the token is not valid before this time (optional)
jtiJWT ID: a unique ID for the token (optional)

You can also add custom claims, such as a user's role, permissions, or tenant ID.

The JWT payload is not encrypted by default. It is only Base64URL encoded, which means anyone who has the token can decode it and read the contents. So you should never put passwords, API keys, or other sensitive secrets inside a normal JWT payload.

Also be careful with claims that change often. If a user's role changes from admin to user, an old token that still says admin may stay valid until it expires.

The payload carries the information. The signature is what allows us to trust that information.

6. The Signature

The third part of a JWT is the signature. This is what allows the receiving server to verify that the token was created by a trusted issuer and that its contents have not been changed.

The signature is created from the encoded header and payload. It is computed like this:

The exact signing key depends on the algorithm being used:

ModelExample algorithmsHow it worksGood fit
SymmetricHS256The same shared secret creates and verifies the signatureOne issuer and a small set of trusted verifiers
AsymmetricRS256, ES256The issuer signs with a private key, other services verify with the matching public keyMany services verifying tokens from one issuer

Now imagine an attacker changes the role in the payload from user to admin. The encoded payload changes, so the existing signature no longer matches. When the server verifies the token, the signature check fails and the token is rejected.

The attacker cannot produce a new valid signature without the signing key.

This is what JWT security relies on. The signature does not hide the payload. It proves that the signed contents have not been modified and, when the signing key is properly protected and trusted, that they came from the expected issuer.

7. The Authentication Flow

Now let's see how the complete flow works in practice.

1. Email and password2. Verify credentials, create and sign JWTJWT3. GET /ordersAuthorization: Bearer JWT4. Verify signature, exp, iss, audOrdersClientAuth ServerAPIClientAuth ServerAPI
6 / 6
algomaster.io

Suppose a user logs into an application using their email and password. The client sends those credentials to the authentication server. The server verifies them, and if they are correct, it creates a JWT containing claims about the user, such as their user ID, role, and expiration time. The server then signs the token and sends it back to the client.

In code, issuing the token might look like this:

From that point on, the client includes the JWT with requests to protected APIs. A common way to send it is in the Authorization header of the HTTP request:

When an API receives the request, it extracts the token and verifies its signature. It also checks important claims:

  • exp: Has the token expired?
  • iss: Did it come from the expected issuer?
  • aud: Was it intended for this API?

If all of those checks pass, the API can trust the claims inside the token and process the request.

If validation fails, the API returns 401 Unauthorized. If the token is valid but does not grant enough permission for the operation, it returns 403 Forbidden.

Note that the token is a bearer credential. Whoever holds it can use it until it expires, so it must only be sent over HTTPS.

In OAuth 2.0 and OpenID Connect setups, the application usually does not handle the user's password directly. It redirects the user to an identity provider and receives tokens after sign-in succeeds. The way APIs use the token afterward is the same.

8. JWTs Across Multiple Services

JWTs are most useful when authentication has to work across many services.

Imagine a system with an API gateway, a user service, an order service, and a payment service. The user logs in once through the authentication service. The authentication service verifies the credentials, creates a JWT, signs it with its private key, and sends it back to the client.

Now the client can include that same access token with requests to different backend services. Each service can verify the JWT using the authentication service's public key.

This reduces the need for a centralized session lookup on every request and avoids making the authentication service part of the critical path for every API call.

Publishing Public Keys with JWKS

How do the services get the public key? Most authentication servers publish their public keys at a JWKS endpoint (JSON Web Key Set), which is simply a URL that returns the current keys:

Each service fetches and caches this key set. When a token arrives, the service reads the kid from the header and picks the matching public key.

This is also how key rotation works without breaking anything:

  1. Publish the new public key in the JWKS.
  2. Start signing new tokens with the new private key.
  3. Keep the old public key available until all tokens signed with it have expired.
  4. Remove the old key.

The JWKS URL and the expected issuer must come from the service's own configuration, never from the token itself.

9. Access Tokens and Refresh Tokens

In practice, authentication systems often use two different tokens: an access token and a refresh token.

The access token is what the client sends to APIs. It is usually short-lived, often lasting anywhere from a few minutes to around an hour. If the access token is a JWT, the API can verify it and use its claims to process the request.

The refresh token serves a different purpose. It is longer-lived and is used to obtain a new access token after the old one expires.

Later, the access token expiresLog inAccess token + refresh tokenRequest with access token200 OKRequest with expired access token401 UnauthorizedRefresh tokenNew access tokenRequest with new access token200 OKClientAuth ServerAPIClientAuth ServerAPI
11 / 11
algomaster.io

So the flow looks like this. The user logs in and receives both an access token and a refresh token. The client uses the access token for normal API requests. Eventually, that access token expires. Instead of asking the user to log in again, the client sends the refresh token to the authentication server. If the refresh token is still valid, the server issues a new access token.

This split balances security and convenience. Access tokens can remain short-lived, which limits the damage if one is stolen, while refresh tokens allow the user to stay signed in for much longer.

Refresh tokens are usually stored more securely and often tracked server-side so they can be revoked, rotated, or invalidated. A refresh token also does not have to be a JWT at all. In many systems, it is simply a long, random, opaque value. And it should only ever be sent to the authentication server, never to normal APIs.

If you use OpenID Connect, you will also see an ID token. It tells the client application who signed in. It is not meant for APIs, and an API should not accept it as an access token.

TokenSent toLifetimePurpose
Access tokenAPIsMinutes to an hourGrants access to APIs
Refresh tokenAuth server onlyDays to weeksGets new access tokens
ID token (OIDC)Read by the client appShortTells the client who signed in

This separation also helps with one of the biggest challenges of JWT-based authentication: revocation, which we cover in section 11.

10. Storing Tokens in Browser Applications

For browser applications, one common question is where the JWT should be stored. Two popular options are local storage and cookies.

Local Storage

Local storage is simple. The application can save the token there and attach it to API requests using the Authorization header.

But local storage is accessible from JavaScript. So if your application has an XSS (cross-site scripting) vulnerability, malicious JavaScript running on the page may be able to read the token and send it to a server the attacker controls.

HTTP-Only Cookies

Another option is an HTTP-only cookie. With an HTTP-only cookie, JavaScript cannot directly read the token, which reduces the risk of token theft through XSS. The browser sends that cookie automatically with requests to the matching domain.

But cookies introduce another security concern: CSRF, or Cross-Site Request Forgery. Because the browser attaches the cookie automatically, another site may be able to trigger a request the user did not intend. That risk can be reduced using SameSite cookies, CSRF tokens, and careful cookie configuration.

StorageMain riskHow to reduce it
Local storageJavaScript can read the token after an XSS bugPrevent XSS, use a strict Content Security Policy, keep tokens short-lived
HTTP-only cookieSent automatically, so CSRF is possibleSameSite cookies, CSRF tokens, Secure flag

So there is no single storage option that is always correct. The right choice depends on your application architecture and threat model.

Regardless of where the token is stored, access tokens should generally be short-lived, transmitted only over HTTPS, and protected from unnecessary exposure.

Outside the browser, use the platform's secure storage. Mobile apps should use the iOS Keychain or Android Keystore, not plain files. Backend services should keep their credentials in a secrets manager rather than hardcoding long-lived tokens.

11. Revocation

The hardest operational problem with JWTs is revocation, which means making a token stop working before its expiration time.

A self-contained JWT can be verified without contacting the issuer. That is good for speed and availability, but it also means the issuer cannot pull the token back once it is out. If a user logs out, changes their password, or loses their phone, the token can stay valid until it expires.

Common ways to handle this:

StrategyHow it worksTrade-off
Short access-token lifetimeTokens expire quickly, such as 5 to 15 minutesA stolen token still works until it expires
Refresh token rotationEach refresh returns a new refresh token, and the old one stops workingThe auth server has to track refresh tokens
Deny listStore the jti of revoked tokens and check it on each requestAdds a lookup on every request
Token version checkThe token carries a version number that the API compares with the user's current versionAdds a lookup, often only for sensitive actions

Short-lived access tokens plus refresh-token rotation is the most common setup. For high-risk actions, such as changing payout details or creating API keys, many systems also check current server-side state even when the JWT is valid.

12. Trade-offs and When to Use JWTs

JWTs are useful, but they are not always the best choice.

For a simple monolithic application, traditional server-side sessions are often easier to implement and reason about. The server stores the session, the client keeps a session ID, and revoking access is as simple as deleting that session.

JWTs can also be a poor fit when you need immediate revocation. Because a self-contained JWT can remain valid until it expires, invalidating it early usually requires extra mechanisms such as a deny list, a token version check, or server-side session state. At that point, you lose some of the simplicity of stateless verification.

Another warning sign is using JWTs simply because they sound more modern or scalable. JWTs do not automatically make an application more secure, faster, or more scalable. They are most valuable when you actually need portable tokens that can be verified across multiple systems.

Server-side sessionsJWTs
What the client holdsAn opaque session IDA signed token with claims
How the server identifies the userLooks up the session in a storeVerifies the signature and reads the claims
RevocationDelete the sessionWait for expiry, or add extra checks
Works best forSingle apps and first-party web appsMany services verifying the same token

So if a normal server-side session already solves your authentication problem cleanly, adding JWTs may introduce more complexity than value.

13. Security Checklist

JWTs are easy to parse and easy to validate incorrectly. If you do use them, keep these rules in mind.

Validate More Than the Signature

Check iss, aud, and exp (and nbf when present), then enforce the required permissions. A token issued for billing-api should not be accepted by orders-api just because its signature is valid.

Allowlist Algorithms

Never let the token decide which algorithm to use. Configure the accepted algorithms on the server:

Reject alg: "none". A classic attack is RS256-to-HS256 confusion: the attacker signs a forged token with alg: "HS256", using the issuer's public key as the HMAC secret. If the API trusts the header and uses that public key as the secret, the forged token passes. Fixing the allowed algorithms on the server prevents this.

Allow a Little Clock Skew

The issuer's and the API's clocks can drift by a few seconds. Most APIs allow a small leeway, around 30 to 60 seconds, when checking exp and nbf. Do not stretch it into minutes.

Keep Claims Small and Stable

JWTs are sent on every request, so large tokens add size to every call and can hit header limits at gateways. Keep claims to what the API needs. Roles in a token are snapshots, so check current server-side state for sensitive decisions.

Do Not Log Tokens

Tokens leak through authorization headers in proxy logs, error logs, analytics events, and crash reports. Redact them before logging.

Separate Environments

Production, staging, and development should use different issuers, audiences, and signing keys, so a staging token can never work against production.

Common Mistakes

  1. Treating JWTs as encrypted: Signed JWTs are readable by anyone holding them.
  2. Skipping aud validation: Tokens meant for one API can be replayed to another.
  3. Trusting the alg header: The server must enforce allowed algorithms.
  4. Using long-lived access tokens: Stolen tokens stay usable for too long.
  5. Using ID tokens as access tokens: ID tokens are for the client, not for APIs.
  6. Assuming JWTs remove all server-side state: Refresh tokens, revocation, and risk checks often still need it.

Summary

A JWT is a signed token format, not a complete authentication system. It has three parts: a header that describes how the token was signed, a payload that carries the claims, and a signature that proves the claims were not changed.

Its main strength is that any service with the issuer's public key can verify the token locally, without a session lookup. That makes JWTs a good fit when many services need to identify the same user.

The main costs are revocation and exposure. A token stays valid until it expires, and anyone who holds it can read and use it. So keep access tokens short-lived, pair them with refresh tokens, validate issuer, audience, and expiry, allowlist algorithms, keep secrets out of the payload, and store tokens carefully.

And if a normal server-side session already solves your problem, it is often the simpler choice.

Quiz

JWT Quiz

10 quizzes