DEV Community

Cover image for The Definitive Guide to JWT Tokens
Kamya Shah
Kamya Shah

Posted on

The Definitive Guide to JWT Tokens

The Definitive Guide to JWT Tokens

JSON Web Tokens, defined in RFC 7519, provide a compact and self-contained open standard for transmitting cryptographically verified claims between distributed systems. Modern web architectures, microservices, and API gateways rely heavily on this standard to handle authentication, federated identity, and fine-grained authorization without requiring shared session storage across every server.

While the convenience of stateless tokens is well understood, improper implementations frequently introduce critical vulnerabilities. Misconfigured algorithms, unvalidated claims, and insecure token storage continue to compromise production systems. This guide examines how JSON Web Tokens work under the hood, how to structure and verify them securely, and how to apply the defensive patterns outlined in modern security standards.

Anatomy of a JSON Web Token

A JSON Web Token (JWT) is most commonly represented as a compact string built according to the RFC 7515 JSON Web Signature (JWS) specification. In its serialized format, a JWT consists of three separate components separated by ASCII period characters:

<Header>.<Payload>.<Signature>
Enter fullscreen mode Exit fullscreen mode

Each segment is encoded using Base64URL without padding, making the token safe to pass inside HTTP headers, query strings, and form parameters. Base64URL replaces standard Base64 characters (+ and /) with URL-safe alternatives (- and _) and strips trailing equal signs (=).

eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImF1dGgtMjAyNi0wMSJ9
.eyJpc3MiOiJodHRwczovL2F1dGguZXhhbXBsZS5jb20iLCJzdWIiOiJ1c3JfMGExYjJjM2QiLCJhdWQiOiJodHRwczovL2FwaS5leGFtcGxlLmNvbSIsImV4cCI6MTc2NzIyMjQwMCwibmJmIjoxNzY3MjE4ODAwLCJpYXQiOjE3NjcyMTg4MDB9
.dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk...
Enter fullscreen mode Exit fullscreen mode

Base64URL encoding is not encryption. Anyone with access to the raw token string can decode the header and payload segments to inspect their contents in plaintext. Confidentiality requires encrypting the token via RFC 7516 JSON Web Encryption (JWE), or ensuring transport-level protection via TLS.

The Header

The header declares cryptographic metadata telling the recipient how to interpret and verify the signature:

{
  "alg": "RS256",
  "typ": "JWT",
  "kid": "auth-2026-01"
}
Enter fullscreen mode Exit fullscreen mode

The header properties serve specific technical functions:

  • alg (Algorithm): Specifies the cryptographic algorithm used to secure the token. Common values include RS256 (RSA signature with SHA-256), ES256 (ECDSA using P-256 and SHA-256), and HS256 (HMAC with SHA-256).
  • typ (Type): Indicates the media type. Standard JWTs declare "JWT", while modern specifications such as RFC 9068 recommend explicit typing such as "at+jwt" for OAuth 2.0 access tokens to mitigate cross-context replay.
  • kid (Key ID): A hint indicating which specific key from a JSON Web Key Set (JWKS) signed the payload, allowing seamless key rotation without downtime.

The Payload

The payload contains claims: assertions made about a subject along with contextual metadata. The specification groups claims into three distinct categories:

  1. Registered Claims: Predefined, standardized keys designed to ensure interoperability across identity providers and services.
  2. Public Claims: Custom claims defined in public registries or given collision-resistant namespace URIs (e.g., https://example.com/claims/tier).
  3. Private Claims: Bespoke keys agreed upon by cooperating parties for internal application logic (e.g., tenant_id or permissions).
{
  "iss": "https://auth.example.com",
  "sub": "usr_0a1b2c3d",
  "aud": "https://api.example.com",
  "exp": 1767222400,
  "nbf": 1767218800,
  "iat": 1767218800,
  "jti": "8f4857b2-6dbb-4395-83e8-857e4e117b35",
  "tenant_id": "org_99182",
  "roles": ["developer", "billing_admin"]
}
Enter fullscreen mode Exit fullscreen mode

The Signature

The signature guarantees message integrity and authenticity. It is calculated by taking the Base64URL-encoded header, appending a period, appending the Base64URL-encoded payload, and feeding that composite string into the declared cryptographic signing function:

Signature = CryptoFunction(
    Base64URL(Header) + "." + Base64URL(Payload),
    SecretOrPrivateKey
)
Enter fullscreen mode Exit fullscreen mode

If an attacker alters a single character inside either the header or payload, the recipient's signature check fails immediately, triggering a request rejection.

Three precision-machined interlocking geometric plates made of polished brass, obsidian, and copper resting in perfect a

Understanding Standard Claims

When validating a JWT, inspecting the cryptographic signature confirms that the data has not been modified in transit. However, signature verification alone is insufficient; the consumer must also enforce the registered assertions defined in RFC 7519 Section 4.1:

Claim Name Format Verification Requirement
iss Issuer String or URI Must exactly match the trusted identity provider URI.
sub Subject String Must represent a valid principal or user identifier within the system.
aud Audience String or Array Must match the resource server's recognized identifier.
exp Expiration Time NumericDate Token must be rejected if current time >= exp (with small clock skew).
nbf Not Before NumericDate Token must be rejected if current time < nbf.
iat Issued At NumericDate Records token creation time; useful for checking token freshness.
jti JWT ID Unique String A one-time identifier used to prevent token replay attacks.

Failing to validate the aud (Audience) claim is a frequent architectural bug. If Service A and Service B both trust the same identity provider, but Service B never checks that incoming tokens contain its own audience identifier, a compromised token issued exclusively for Service A can be presented to Service B to hijack resources.

Cryptographic Signing: Symmetric vs. Asymmetric

Choosing an appropriate signature scheme depends directly on system boundary topologies.

Symmetric Signing (HMAC)

Symmetric algorithms (HS256, HS384, HS512) use the exact same secret key to both produce the signature and verify it.

Token Creation:   HMAC-SHA256(Data, SharedSecret) -> Signature
Token Validation: HMAC-SHA256(Data, SharedSecret) == Signature
Enter fullscreen mode Exit fullscreen mode
  • Advantages: Extremely fast execution with minimal compute overhead.
  • Drawbacks: Every service that verifies the token must possess the secret. If one internal microservice is compromised, the attacker acquires the signing key and can forge arbitrary tokens for every other service in the fleet.
  • Recommended Use: Tightly coupled monoliths or closed services where token generation and verification happen within the same trust zone.

Asymmetric Signing (RSA, ECDSA, EdDSA)

Asymmetric algorithms split key material into a private key (retained exclusively by the authorization server) and a public key (distributed to resource servers).

  • RSA (RS256, PS256): The authorization server signs with an RSA private key (minimum 2048-bit; 3072-bit recommended); consumers verify using the public key. PS256 (RSASSA-PSS) uses probabilistic padding, providing stronger cryptographic bounds than older PKCS#1 v1.5 padding (RS256).
  • ECDSA (ES256, ES384): Elliptic Curve signatures offer identical security strength to RSA at a fraction of the key size (a 256-bit P-256 key matches the strength of a 3072-bit RSA key), reducing network transmission size.
  • EdDSA (Ed25519): Uses twisted Edwards curves to provide deterministic, high-speed signatures with built-in resistance to side-channel attacks.

Managing Keys with JWKS

Asymmetric architectures publish their public verification keys via a standard JSON Web Key Set (JWKS) endpoint, typically exposed at /.well-known/jwks.json:

{
  "keys": [
    {
      "kty": "RSA",
      "use": "sig",
      "alg": "RS256",
      "kid": "auth-2026-01",
      "n": "u1WDE65WpG...",
      "e": "AQAB"
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

Downstream consumers cache this document in memory and refresh keys when encountering an unrecognized kid, enabling background key rotation without interrupting active users.

Stateless vs. Stateful: Managing Revocation

The core benefit of a JWT is statelessness: services verify signatures locally without executing a database lookup per request. However, this architectural benefit is also its primary operational trade-off: a standard JWT cannot be revoked before it expires.

If an account is suspended, permissions change, or a device is reported stolen, an issued access token remains valid until its exp timestamp passes. Teams handle this reality using two main approaches:

1. Short-Lived Access Tokens and Refresh Token Rotation

The industry standard approach pairs short-lived access tokens (5 to 15 minutes) with long-lived, stateful refresh tokens stored in an authenticated database.

  • Clients use the short-lived JWT for high-frequency API calls.
  • When the access token expires, the client contacts the authorization server with its refresh token.
  • The authorization server validates the refresh token against its datastore, verifies that the account is active, issues a new short-lived JWT, and rotates the refresh token (invalidating the prior one).
  • If an account is suspended, the administrator revokes the refresh token. The user's active session terminates within minutes as the current access token lapses.

2. Distributed Revocation Lists

For workloads requiring immediate revocation, architectures maintain a high-speed memory cache (such as Redis) tracking revoked token IDs (jti) or account-wide revocation timestamps (tokens_issued_before). While this introduces a remote lookup, it requires storing only invalidated keys rather than active session state for every user.

JWT Authentication in Modern Infrastructure

In enterprise systems, individual downstream services rarely implement raw token validation routines themselves. Instead, verification is delegated to edge routers, reverse proxies, or dedicated gateways.

Beyond internal microservices, modern API gateways handle token verification and access control centrally. For example, Bifrost, an open-source AI gateway built by Maxim AI, validates upstream authentication tokens and maps enterprise identity to granular permissions. Beyond routing, Bifrost applies governance and security controls (virtual keys, budgets, guardrails, audit logs) centrally, and Bifrost Edge extends that same governance and security to AI traffic on employee machines, with endpoint enforcement on each device.

Terminating and validating authentication tokens at the perimeter prevents unauthenticated requests from consuming internal compute resources, while ensuring unified audit logging across every environment.

A fortified architectural archway gate equipped with mechanical tumblers, optical lenses, and an automated verification

Critical Security Vulnerabilities and Defensive Mitigations

Because JWT validation relies on metadata transmitted within the token itself, software libraries that blindly trust header properties have historically suffered from severe vulnerabilities.

The IETF published RFC 8725: JSON Web Token Best Current Practices to formally codify defenses against these common attack vectors:

1. The Algorithm Confusion Attack (RS256 vs. HS256)

In this attack, a service expects tokens signed with an asymmetric algorithm (RS256) and has access to the identity provider's public key.

An attacker modifies the header to specify symmetric HMAC ("alg": "HS256") and signs the token using the target server's publicly accessible RSA public key as the HMAC shared secret. If the backend verification library relies on the incoming alg header to select its verification routine, it runs verifyHMAC(token, rsaPublicKey). Because the attacker signed the payload using that same public key, the cryptographic check succeeds, granting arbitrary administrative privileges.

  • Mitigation: Never allow the token header to determine the verification algorithm. Hardcode the acceptable algorithms in your server configuration (e.g., algorithms=["RS256"]) and reject any token declaring unexpected algorithms.

2. The alg: none Vulnerability

RFC 7515 includes an algorithm identifier named none, originally intended for debugging unsecured tokens. Early token parsing libraries accepted tokens signed with "alg": "none" and bypassed signature validation entirely.

  • Mitigation: Explicitly disable the none algorithm in your validation pipeline. All reputable modern libraries forbid none by default unless explicitly configured otherwise.

3. Key ID (kid) Header Injections

The kid header specifies which key to fetch. Attackers have historically exploited dynamic lookups by injecting malicious parameters:

  • Directory Traversal: Setting "kid": "../../../dev/null" to force the server to verify against an empty key file.
  • SQL Injection: Injecting malicious SQL into database key-lookup queries ("kid": "key' UNION SELECT 'attacker-secret'--").

  • Mitigation: Treat header values as untrusted user input. Sanitize the kid parameter, match it strictly against an in-memory allowlist, or restrict lookups to predefined static maps.

4. Insecure Client-Side Storage

Where web clients store JWTs directly affects vulnerability to account hijacking:

  • localStorage / sessionStorage: Readily accessible to JavaScript. Any Cross-Site Scripting (XSS) vulnerability inside the application or its third-party npm dependencies allows attackers to extract the token instantly.
  • HttpOnly, Secure, SameSite=Strict Cookies: Inaccessible to JavaScript, defending the token against direct extraction via XSS. Cross-Site Request Forgery (CSRF) must then be mitigated using modern SameSite flags or anti-CSRF challenge tokens.

Step-by-Step Implementation: Secure Verification in Node.js

The following example demonstrates defensive token verification in Node.js using the standard jose library, adhering strictly to RFC 8725 guidelines:

import { jwtVerify, createRemoteJWKSet } from 'jose';

// Establish a cached, rate-limited remote JWKS lookup
const JWKS_URI = new URL('https://auth.example.com/.well-known/jwks.json');
const JWKS = createRemoteJWKSet(JWKS_URI, {
  cacheMaxAge: 600000, // 10 minutes
  cooldownDuration: 30000, // Prevent JWKS scraping loops
});

const EXPECTED_ISSUER = 'https://auth.example.com';
const EXPECTED_AUDIENCE = 'https://api.example.com';

export async function authenticateRequest(authHeader) {
  if (!authHeader || !authHeader.startsWith('Bearer ')) {
    throw new Error('Missing or malformed Authorization header');
  }

  const token = authHeader.slice(7).trim();

  try {
    // Cryptographic validation enforcing algorithm and claims constraints
    const { payload, protectedHeader } = await jwtVerify(token, JWKS, {
      issuer: EXPECTED_ISSUER,
      audience: EXPECTED_AUDIENCE,
      algorithms: ['RS256', 'PS256'], // Explicit allowlist prevents algorithm confusion
      clockTolerance: '15 seconds', // Accommodate minor server time drift
    });

    // Verify application-specific assertions
    if (!payload.sub) {
      throw new Error('Token payload missing mandatory subject claim');
    }

    return {
      userId: payload.sub,
      roles: payload.roles || [],
      tenantId: payload.tenant_id,
    };
  } catch (error) {
    // Provide sanitized operational error codes without leaking internal failure details
    throw new Error(`Token verification failed: ${error.message}`);
  }
}
Enter fullscreen mode Exit fullscreen mode

Production Checklist for Engineering Teams

Before deploying JWT authentication to production, confirm your implementation addresses these baseline requirements:

  • [ ] Algorithm Constraints: Verification routines enforce an explicit algorithm allowlist (RS256, ES256, etc.); the none algorithm is permanently disabled.
  • [ ] Asymmetric Key Separation: Public verification keys are distributed via JWKS; private signing keys are isolated in secure vaults or managed Identity Providers.
  • [ ] Claim Enforcement: The iss, aud, and exp claims are strictly validated on every incoming request.
  • [ ] Short Lifespans: Access token validity does not exceed 15 minutes, with long-lived session continuity handled via refresh token rotation.
  • [ ] Secure Storage: Web browser clients store access tokens in HttpOnly, Secure cookies with appropriate SameSite settings rather than localStorage.
  • [ ] Payload Sanitization: Tokens contain only non-sensitive identity metadata and role references, avoiding confidential secrets or passwords.

JSON Web Tokens provide an effective, scalable foundation for distributed authorization when implemented with discipline. By standardizing on asymmetric cryptographic signing, enforcing registered claims, and validating tokens at your network perimeter, you can build stateless architectures that remain robust against modern attack techniques.

Sources

Top comments (0)