What Is a JSON Web Token (JWT) and How Does It Work in Modern Web Apps?

Secure modern web apps using JSON Web Tokens (JWT). Learn what they are, how they work, and their benefits in stateless architectures.

Clifford Spielman |

Though JWTs, or JSON Web Tokens, aren't used in all modern apps, they are commonly leveraged by APIs, distributed systems, microservices, and Single-Page Applications (SPAs). The seamless experience you expect from modern apps — staying signed in while using the app and any services it calls behind the scenes — is often powered by tokens. And in many cases, those tokens are JWTs.

JWTs are a compact and tamper-resistant means of passing user-related information such as identity and token expiration between systems. They are also Base64Url-encoded, which makes them safe to include in HTTP headers, cookies, or even URLs without needing additional encoding.

It's not a stretch to say they are fundamental building blocks in many token-based authentication systems and play a crucial role in stateless security architectures that power everything from SPAs to APIs.

What Is a JWT?

A JWT is a digitally-signed token that shares information — known as claims — between parties. These claims are pieces of information such as a user's ID or token expiration timestamp that help systems identify the user and confirm the token is still valid. Depending on its purpose, a JWT may also include additional standardized or custom claims. JWTs are widely used in modern web applications.

Unlike traditional session-based authentication, which stores user state on the server, JWTs are stateless: all the data the server needs to verify a user's identity is embedded within the token itself. Once a JWT is issued, clients include it in future requests (typically via an HTTP Authorization header), allowing services to validate access without having to store session data.

JWTs are compact by design, which lends itself to efficient transmission. At their core, they are JSON objects that are encoded into a compact Base64Url string that fits easily in HTTP headers, cookies, or even URLs. This makes them ideal for environments where payload size matters, such as SPAs and also APIs, including those designed to support mobile clients.

A JWT is often described as self-contained, meaning it carries its claims inside the token itself, so they can be read and validated without consulting the system that issued the token.

Other key characteristics of JWTs include tamper-resistance and verifiability. This is achieved through cryptographic signing using a secret (HMAC) or private key (RSA/ECDSA), which allows recipients to confirm that the token hasn't been altered.

JWTs are widely used in OAuth 2.0 and OpenID Connect frameworks. They are also supported by popular platforms such as Duende Software's IdentityServer, which help developers implement secure token-based authentication workflows.

Structure of a JWT

A JWT consists of three sections, separated by a period (.) as follows:

header.payload.signature

Each part is a Base64Url-encoded JSON object and serves a specific function:

Header: This section specifies metadata about the token, usually the signing algorithm and token type. The algorithm defines how the token is signed. Some are symmetric and use a shared secret (for example, HS256), while others are asymmetric and use a key pair (for example, RS256, ES256, and PS256).

Algorithms like HS256 are simpler, faster, and a common choice when both the issuer and consumer of the token run in the same trusted environment. However, asymmetric algorithms are often preferred for stronger security and because the public key can be safely distributed.

Regarding token type, other common values are used in specific contexts, such as at+jwt for access tokens, id_token for identity tokens, and secevent+jwt for security event tokens.

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

Payload: This section contains the claims, or the data being transmitted, such as the user ID and when the token expires. Some are standardized claims, such as sub for the user ID and exp for the token expiration timestamp. dept is an example of a custom claim that stores the authenticated user's department. Also note that depending on the purpose of the JWT, the standardized claims may vary.

{
  "sub": "1234",
  "name": "Jane Doe",
  "dept": "accounting",
  "exp": 1717257600
}

Signature: This section is a cryptographic signature created using the encoded header/payload, along with a secret key (or private key for asymmetric signing). It ensures the token hasn't been altered, and when asymmetric signing is used, it also provides non-repudiation by proving that the token came from a trusted source.

Together, these components allow the server to verify the authenticity of the token and trust the claims it contains without having to query a database on every request. If you want to explore the structure and contents of your own JWTs, or even just look at a canned example, check out Duende Software's free JWT Decoder tool. It also keeps your JWT data safe by working with it locally.

How JWTs Work

In modern web applications, a JWT is typically issued after a user successfully authenticates. The authorization server (such as Duende IdentityServer) generates a token that encapsulates the user's identity and any relevant claims. This token is then passed to the client, which should store it securely, preferably using cookies with Secure, HttpOnly, and a couple of additional settings activated. This is to prevent Cross-Site Scripting (XSS) and Cross-Site Request Forgery (CSRF) attacks.

On subsequent requests, the client includes the JWT in the Authorization header using what's known as the Bearer scheme:

Authorization: Bearer <your-token-here>

The server receives the token, verifies the signature using its secret key or public key (depending on the algorithm), and checks the claims. If the token is valid and not expired, then access is granted, all without requiring the server to maintain session state.

This stateless nature makes JWTs a popular choice for:

  • Single-Page Applications (SPAs), where statelessness, decentralization, and API reliance are the default.
  • Distributed systems and microservices, where stateless authentication lets each service verify requests without depending on a central session store.
  • APIs, where stateless, token-based authentication allows each request to be independently validated. This isn't limited to REST, and can also apply to GraphQL, WebSockets, and other service types.

Common Use Cases for JWTs

JWTs are commonly used to solve a variety of identity and access-related problems in modern applications. Here's how JWTs show up in real-world scenarios you might already be familiar with:

User Authentication: JWTs are often used after a user logs in to an app. The token allows the user to remain authenticated while navigating between secure sections without re-entering their credentials.

API Authorization: A frontend application might include a JWT in the Authorization header when calling an API endpoint, and the backend uses the token's claims to validate whether the request is allowed.

Single Sign-On (SSO): An employee logs into an Identity Provider (IdP), receives an ID token (typically a JWT), and can access multiple internal apps like HR tools, email, and calendars — all without logging in again.

Impersonation and Delegation: JWTs can include claims that indicate whether a user is acting on behalf of another, which is helpful in admin tools or delegated access scenarios.

Short-Term Access with Refresh Tokens: JWTs used to grant access to resources should be short-lived to reduce risk. Paired with long-lived refresh tokens, this approach allows seamless token renewal without forcing the user to log in again.

In platforms such as Duende IdentityServer, JWTs are used extensively within OAuth 2.0 and OpenID Connect flows to represent identity and access rights in a standardized, interoperable way.

Are JWTs Perfect?

While JWTs have become indispensable in stateless and distributed systems, they also come with tradeoffs. Token expiration must be handled thoughtfully, especially in scenarios where you need the ability to revoke tokens centrally. In those cases, consider alternatives such as reference tokens, which are opaque (not readable by the client) identifiers that are stored and managed server-side. For example, a system such as Duende IdentityServer can issue JWTs by default for access, but also offers the option of issuing reference tokens instead, which can be revoked on demand.

Storing JWTs securely on the client requires sound decision making. For example, Secure / HttpOnly cookies are recommended over other local storage options to help prevent XSS and CSRF attacks.

In terms of token size, because JWTs themselves don't have a size limit per se — other than limits imposed by the HTTP protocol — they can become large if overloaded with claims, which may affect transmission efficiency. In fairness to JWTs, this isn't a typical scenario but is worth mentioning.

As with any architecture decision, understanding your application's requirements as well as any limitations of JWTs will help determine whether they are the right fit for your development.

We've discussed what a JWT is, as well as its core features and uses, but a clarification regarding security is warranted. While JWTs can play a central role in secure identity systems, they are not inherently secure on their own. If a hacker or unintended recipient intercepts a token, they can view its contents unless the token is encrypted using a method like JSON Web Encryption (JWE). Signing protects against tampering, but not against exposure.

If you're building secure web applications or APIs and want to explore token-based authentication more deeply, platforms such as Duende Software's IdentityServer and their documentation library provide a great starting point.