Skip to content

Services, Wiki-Artikel, Blog-Beiträge und Glossar-Einträge durchsuchen

↑↓NavigierenEnterÖffnenESCSchließen
Identity & Access Management Glossary

JSON Web Token (JWT)

JSON Web Tokens (JWTs) are compact, signed tokens used to securely transmit user identities and claims between systems. JWTs serve as the foundation for modern API authentication and single sign-on—but if implemented incorrectly, they can be a common security vulnerability.

JSON Web Tokens (JWT) are an open standard (RFC 7519) for the secure exchange of information as JSON objects. They are used by OAuth 2.0 and OpenID Connect as ID tokens and access tokens—and are also one of the most common sources of authentication bugs in modern web applications.

JWT Structure - Header.Payload.Signature

A JWT consists of three Base64URL-encoded parts separated by dots. The following example shows a decoded JWT:

eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VyXzEyMyIsInJvbGUiOiJhZG1pbiIsImV4cCI6MTcxMTQ4MDAwMH0.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

The header (after Base64URL decoding) contains the algorithm used:

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

The payload (after Base64URL decoding) contains the claims:

{
  "sub": "user_123",
  "iss": "https://auth.firma.de",
  "aud": "api.firma.de",
  "iat": 1711393600,
  "exp": 1711480000,
  "role": "admin",
  "email": "max@firma.de"
}

The signature is calculated as:

RSASHA256(base64url(header) + "." + base64url(payload), privateKey)

> Important: The payload is ONLY Base64URL-encoded, not encrypted. Anyone can decode and read the payload. The signature prevents tampering, but not reading. Do not include sensitive data such as passwords or Social Security numbers in the payload.

JWT in Practice: Issuance and Validation

Login Flow with JWT

The process begins with the client sending credentials to the auth server. The server verifies the credentials, creates a signed JWT, and responds with an access token, refresh token, and the expiration time (expires_in: 3600).

API Request with JWT

The client sends the token in the Authorization: Bearer header. The API server validates four points: check signature, exp > now, iss == known, and aud == this API.

The validation in Node.js looks like this:

import { verify } from 'jsonwebtoken';

function validateJWT(token: string): JWTPayload {
  try {
    const payload = verify(token, publicKey, {
      algorithms: ['RS256'],         // Only RS256 or ES256!
      issuer: 'https://auth.firma.de',
      audience: 'api.firma.de',
    });
    return payload as JWTPayload;
  } catch (err) {
    throw new UnauthorizedError('Invalid token');
  }
}

JWT Algorithms - Critical Comparison

Symmetric Algorithms (HMAC)

AlgorithmPropertiesProblem
HS256Same key for signing and validationAuth server and API must share the key; any API server that validates could also issue JWTs
HS512Stronger than HS256Same fundamental problem

HMAC algorithms are only secure in single-service setups where only one component performs validation.

AlgorithmPropertiesRecommendation
RS256 (RSA + SHA256)Private key only on auth server, public key on all API servers via JWKSStandard for OAuth 2.0 / OIDC
ES256 (ECDSA + SHA256)Elliptic curves, smaller keys, same security as RS256, faster than RSARecommended for new systems

DANGEROUS - Never use

alg: "none" (unsigned JWT): A JWT without a signature. Some libraries accept this—this is the biggest historical JWT vulnerability. Attackers change the header to "alg":"none" and manipulate the payload at will.

Algorithm Confusion (alg: "HS256" when RS256 is expected): An attacker signs using the public key as the HS256 secret. If the server reads the alg header without validation, the token is accepted as valid. Fix: Always hardcode the algorithm in the code; never read it from the header.

JWT Security Vulnerabilities

1. Algorithm Confusion (alg-Swap)

The attacker changes the header from {"alg": "RS256"} to {"alg": "HS256"} and signs using the publicly available RS256 public key as the HMAC secret. The server reads the alg header and verifies it using the public key as the HS256 secret—the token is considered valid, and the attacker can set any claims.

Fix: Hardcode the algorithm:

verify(token, publicKey, { algorithms: ['RS256'] })  // NEVER ['RS256', 'HS256']!

2. "none" Algorithm

Tokens with alg: "none" do not require a signature; the payload can be manipulated arbitrarily (e.g., role: "admin").

Fix: Check the library—verify() must enforce an algorithm list.

3. Missing expiration (exp)

A JWT without an exp claim or with a very long validity period remains permanently valid after being stolen.

Fix: Always set exp; maximum 1 hour for access tokens, maximum 7–30 days for refresh tokens.

4. Signature not validated

Some APIs only check if a token is present, not if the signature is correct—decode is not the same as verify.

  • Wrong: jwt.decode(token)—only decodes, no signature verification
  • Correct: jwt.verify(token, publicKey)

5. Sensitive data in the payload

Since the JWT payload is only Base64URL-encoded, password hashes, Social Security numbers, or account numbers are visible to anyone. The payload should only contain non-sensitive claims that are necessary for authentication decisions.

6. Token in localStorage (browser)

Data in localStorage is readable by JavaScript—an XSS attack can steal the token.

Better: httpOnly Secure Cookie (inaccessible to JavaScript). For SPAs: In-memory storage plus a refresh token in an httpOnly cookie.

7. Missing Audience (aud) Check

A token for Service A (aud: "service-a") is sent to Service B, which accepts it—this is called token substitution.

Fix: Always explicitly check the aud claim; specify it in verify().

JWKS - Dynamically loading public keys

JWKS (JSON Web Key Set) is the standard for public key distribution. The auth server publishes its public key at GET /.well-known/jwks.json:

{
  "keys": [
    {
      "kty": "RSA",
      "use": "sig",
      "kid": "key-2024-01",
      "n": "0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2...",
      "e": "AQAB"
    }
  ]
}

The API server loads the keys on startup and caches them:

import { createRemoteJWKSet, jwtVerify } from 'jose';

const JWKS = createRemoteJWKSet(
  new URL('https://auth.firma.de/.well-known/jwks.json')
);

const { payload } = await jwtVerify(token, JWKS, {
  issuer: 'https://auth.firma.de',
  audience: 'api.firma.de',
});

Key Rotation: The auth server can rotate keys—the kid claim in the JWT header identifies which key to use. Old tokens remain valid as long as exp has not expired. The JWKS endpoint provides new keys; API servers cache keys with, for example, a 10-minute TTL.

JWT Lifetime and Refresh Token Strategy

TokenDurationReason
Access Token15–60 minutesShort—if stolen, quickly becomes worthless; no revocation issue
Refresh Token7–30 daysFor renewing access tokens; stored securely

Refresh Token Rotation

With every access token refresh, a new refresh token is issued and the old one is invalidated. This enables the detection of token theft: If a stolen refresh token is used while the legitimate user still recognizes the old token as active, the system detects two active refresh tokens and revokes both.

POST /auth/refresh
Cookie: refresh_token=eyJ...

Response:
{ "access_token": "eyJ...", "expires_in": 900 }
Set-Cookie: refresh_token=eyJ...; HttpOnly; Secure; SameSite=Strict

JWT vs. Session Tokens - When to Use Which?

PropertyJWTSession Token
StateStateless (API-validated)Stateful (DB lookup)
RevocationDifficult (until exp!)Immediate (delete token from DB)
ScalabilityEasy (no DB hit)DB access with every request
Size~400–1000 bytes32 bytes (ID only)
PayloadClaims directly in the tokenClaims in the session table

When to use JWT

  • Microservices (Service B validates without querying Service A)
  • API ecosystem with multiple audiences
  • OAuth 2.0 / OIDC (standard defines JWT)

When to use session tokens

  • Traditional web apps with server-side rendering
  • Immediate revocation required (e.g., "Account locked")
  • Smaller data volume per request desired
  • Simpler implementation preferred
  • Access Token: short-lived JWT (15 min)
  • Session: httpOnly cookie with refresh token
  • Revocation via refresh token database