In modern cloud-native architectures, backends are rarely exposed directly to the public Internet as monolithic applications. Instead, systems are decomposed into dozens or hundreds of specialized microservices deployed across containerized clusters. If external clients—such as web single-page applications, native mobile clients, and third-party partner integrations—attempted to interact directly with internal microservices, the architecture would collapse under security vulnerabilities, network chattiness, brittle coupling, and duplicated authentication logic.
The API Gateway pattern serves as the single, hardened, ingress checkpoint for all external traffic. Sitting strategically between external consumers and internal microservices, the gateway handles critical cross-cutting concerns: cryptographic TLS termination, identity authentication, token translation, fine-grained rate limiting, traffic routing, circuit breaking, and telemetry aggregation.
This module delivers an exhaustive, university-grade architectural breakdown of the API Gateway pattern, mathematical formulations of rate limiting algorithms (Fixed Window, Sliding Log, Sliding Counter, Token Bucket, and Leaky Bucket), distributed Redis Lua implementations, and enterprise authentication protocols (OAuth 2.0, OIDC, JWTs, and the Hybrid Reference Token architecture).
The API Gateway Architectural Topology#
The API Gateway acts as an intelligent reverse proxy that abstracts the physical topology of internal microservices from external clients.
[ External Traffic: Web / Mobile / Partners ]
|
v
+-----------------------------------------------------------------------------------------------+
| API Gateway Ingress Perimeter (Kong / Envoy / Traefik / AWS API Gateway) |
| |
| +-----------------------------------------------------------------------------------------+ |
| | Layer 1: Security & Transport Termination | |
| | - TLS 1.3 Termination, DDoS Mitigation, IP Reputation Allow/Deny-Lists | |
| +--------------------------------------------+--------------------------------------------+ |
| v |
| +-----------------------------------------------------------------------------------------+ |
| | Layer 2: Authentication & Token Introspection | |
| | - Validates OAuth2 / JWT / API Key; Translates Opaque Reference Token to Internal JWT | |
| +--------------------------------------------+--------------------------------------------+ |
| v |
| +-----------------------------------------------------------------------------------------+ |
| | Layer 3: Traffic Policy & Rate Limiting | |
| | - Distributed Token Bucket in Redis; Quotas per Tenant / API Key; WAF Inspection | |
| +--------------------------------------------+--------------------------------------------+ |
| v |
| +-----------------------------------------------------------------------------------------+ |
| | Layer 4: Dynamic Routing & Transformation | |
| | - Path Rewriting, Header Enrichment (x-user-id, x-tenant-id), Circuit Breaking | |
| +-----------------------------------------------------------------------------------------+ |
+-----------------------------------------------+-----------------------------------------------+
| Internal Virtual Private Cloud (mTLS / gRPC)
+----------------------------------+----------------------------------+
| | |
v v v
+------------------------+ +------------------------+ +------------------------+
| Order Service | | Payment Service | | Customer Service |
| (Private IP: 10.0.1.5) | | (Private IP: 10.0.2.8) | | (Private IP: 10.0.3.1) |
+------------------------+ +------------------------+ +------------------------+Core Responsibilities of an Enterprise API Gateway:#
- Centralized Identity & Perimeter Defense: Internal microservices do not need to repeat complex OAuth token parsing or database user lookups; the gateway validates identity once at the perimeter and forwards trusted, enriched identity headers (
X-User-Id,X-User-Roles,X-Tenant-Id) downstream over a secure private network. - Protocol Translation & Aggregation (BFF - Backend for Frontend): The gateway can expose public REST/JSON endpoints to mobile clients while communicating with internal microservices over ultra-fast binary gRPC / Protocol Buffers.
- Resilience & Fault Isolation: Implements circuit breakers (Hystrix / Envoy outlier detection) and retry budgets to prevent a cascading failure in a non-critical microservice from taking down the entire platform.
Rate Limiting Algorithms: Mathematical Modeling & Trade-Offs#
Rate limiting is the practice of restricting the number of requests a client can execute within a specified time interval. It is essential for preventing Distributed Denial of Service (DDoS) attacks, enforcing API tier quotas (e.g., Free vs Enterprise), mitigating brute-force credential stuffing, and shielding downstream relational databases from traffic spikes.
+-----------------------------------------------------------------------------------+
| Mathematical Profiles of Rate Limiting Algorithms |
| |
| 1. Fixed Window: |
| - Time partitioned into discrete slices [0-60s), [60-120s). |
| - Flaw: Boundary spike allows 2x burst across interval edge! |
| |
| 2. Sliding Window Log: |
| - Stores timestamp for every request in Redis Sorted Set. |
| - Flaw: O(M) memory explosion under high QPS. |
| |
| 3. Sliding Window Counter (Weighted Moving Average): |
| - Blends previous window with current window: |
| Count = Current_Count + Prev_Count * (1 - Elapsed_Fraction) |
| - Ideal trade-off: O(1) memory, zero boundary burst anomalies. |
| |
| 4. Token Bucket: |
| - Tokens accumulate continuously at rate 'r' up to capacity 'C'. |
| - Allows controlled instantaneous bursts up to capacity 'C'. |
| - Industry standard for general REST APIs. |
| |
| 5. Leaky Bucket (Traffic Shaping): |
| - Requests enter buffer; drain to downstream services at constant rate. |
| - Converts jagged, bursty traffic into smooth, constant-rate streams. |
+-----------------------------------------------------------------------------------+1. Fixed Window Counter#
- Algorithm: Divides time into static windows of size W (e.g., 60 seconds). A counter is incremented for each incoming request:
Key = rate:user_42:202610301400
If Counter > Limit, the request is rejected with 429 Too Many Requests.
- The Window Boundary Spike Vulnerability:
Assume a quota of 100 requests per minute (W = 60 s).
* An attacker sends 0 requests from t = 0 to t = 59.
* At t = 59 s, the attacker bursts 100 requests. All 100 succeed.
* At t = 60 s, a new window begins; the counter resets to 0.
* At t = 61 s, the attacker bursts another 100 requests. All 100 succeed.
Result: The server processed 200 requests within a 2-second interval* (2 × Limit), completely defeating the rate limit and potentially crashing the database.
2. Sliding Window Log#
- Algorithm: Stores the unix timestamp of every single request in a sorted set (e.g., Redis
ZSET).
When a new request arrives at time t:
1. Remove all entries older than t - W (ZREMRANGEBYSCORE key 0 (t - W)).
2. Count remaining elements in set (ZCARD key).
3. If Count < Limit, append current timestamp (ZADD key t t) and allow request.
- Analysis: Eliminates boundary spikes with mathematical precision. However, storing every timestamp consumes O(M) memory. If an API receives 10,000 requests per second per client, the Redis memory footprint explodes into gigabytes.
3. Sliding Window Counter (Weighted Average)#
- Algorithm: Blends the counter of the previous window with the counter of the current window based on the percentage of time elapsed in the current window:
Estimated Count = C_{current} + C_{previous} × ≤ft(1 - (t - t_{window_start}) / (W)) Analysis*: Requires storing only two integer numbers per client (O(1) memory). It prevents boundary spikes with minimal CPU and memory overhead, making it the preferred choice for high-throughput edge proxies (e.g., Cloudflare).
4. Token Bucket Algorithm#
- Algorithm: A bucket has a maximum capacity C. Tokens are added to the bucket at a continuous fill rate of r tokens per second. When a request arrives, it attempts to consume 1 token (or k tokens for heavy operations). If the bucket has sufficient tokens, the request is permitted; if empty, the request is rejected.
- Instantaneous Token Calculation:
Rather than running a continuous background timer to add tokens, the token count is calculated lazily on each request:
Δ t = t_{current} - t_{last_update}New Tokens = min(C, Tokens_{previous} + (Δ t × r)) Key Characteristic*: Allows bursts up to C while strictly capping sustained throughput at r requests per second.
5. Leaky Bucket (Traffic Shaping)#
- Algorithm: Incoming requests are dropped into a FIFO queue (the bucket) of size B. Requests drain out of the queue and are executed by downstream services at a smooth, constant rate of r requests per second. If the queue is full, arriving requests "overflow" and are dropped with HTTP 429.
- Key Characteristic: Traffic shaping. Eliminates all burstiness, converting volatile traffic spikes into a perfectly predictable stream.
IETF Rate Limiting Headers Specification#
When rejecting or throttling clients, APIs must adhere to the standardized IETF Draft headers:
RateLimit-Limit: Maximum permitted request quota in the window (e.g.,100).RateLimit-Remaining: Number of tokens or requests remaining in the current window (e.g.,42).RateLimit-Reset: Number of seconds remaining until quota replenishes (e.g.,18).Retry-After: Required when returning HTTP429 Too Many Requests. Specifies the number of seconds (or HTTP-date) the client must sleep before re-attempting requests.
Distributed Rate Limiting & Atomic Redis Lua Implementation#
In a distributed environment with 20 API gateway instances, local in-memory counters fail because a client's requests are load-balanced across multiple instances. Rate state must be centralized in a high-speed in-memory store like Redis.
However, naive application code executing:
count = redis.get(key); if (count < limit) { redis.incr(key); }
suffers from classic Time-of-Check to Time-of-Use (TOCTOU) race conditions under concurrent requests.
To guarantee absolute atomicity without distributed lock overhead, rate limiting algorithms must execute inside Redis Lua Scripts (EVALSHA), which run atomically within the single-threaded Redis event loop.
Production Token Bucket Redis Lua Script#
-- token_bucket.lua
-- KEYS[1]: Rate limit key (e.g., "rate:token_bucket:user_42")
-- ARGV[1]: Max capacity (C)
-- ARGV[2]: Refill rate per millisecond (r)
-- ARGV[3]: Current timestamp in milliseconds (now)
-- ARGV[4]: Requested tokens (cost, usually 1)
local key = KEYS[1]
local capacity = tonumber(ARGV[1])
local fill_rate = tonumber(ARGV[2])
local now = tonumber(ARGV[3])
local requested = tonumber(ARGV[4])
-- Retrieve current state [tokens, last_update]
local data = redis.call("HMGET", key, "tokens", "last_update")
local current_tokens = tonumber(data[1])
local last_update = tonumber(data[2])
if current_tokens == nil then
-- First request: Initialize full bucket
current_tokens = capacity
last_update = now
else
-- Compute elapsed time and lazy refill
local elapsed = math.max(0, now - last_update)
local generated_tokens = elapsed * fill_rate
current_tokens = math.min(capacity, current_tokens + generated_tokens)
last_update = now
end
if current_tokens >= requested then
-- Deduct tokens and allow request
current_tokens = current_tokens - requested
redis.call("HMSET", key, "tokens", current_tokens, "last_update", last_update)
-- Expire key after time required to refill full capacity
local ttl_seconds = math.ceil(capacity / (fill_rate * 1000)) + 60
redis.call("EXPIRE", key, ttl_seconds)
return { 1, math.floor(current_tokens), 0 } -- [Allowed, Remaining, RetryAfterMs]
else
-- Reject request: Calculate required wait time
local needed = requested - current_tokens
local retry_after_ms = math.ceil(needed / fill_rate)
return { 0, math.floor(current_tokens), retry_after_ms }
endAuthentication & Authorization Architectures#
Securing REST APIs requires decoupling identity verification (Authentication) from permissions enforcement (Authorization).
+-----------------------------------------------------------------------------------+
| Authentication & Authorization Flow in API Gateways |
| |
| [ Client Application ] |
| | |
| | 1. Request with Bearer Token: |
| | Authorization: Bearer <opaque_ref_token> |
| v |
| +-----------------------------------------------------------------------------+ |
| | API Gateway Perimeter | |
| | - Introspects token against Redis Cache / IdP | |
| | - Resolves User Identity: { sub: "u_902", roles: ["admin", "billing"] } | |
| | - Generates cryptographically signed internal JWT (TTL: 60s) | |
| +-------------------------------------+---------------------------------------+ |
| | 2. Enriched Internal Request |
| | Authorization: Bearer <internal_jwt> |
| | X-User-Id: u_902 |
| v |
| +-----------------------------------------------------------------------------+ |
| | Internal Microservice Cluster | |
| | - Verifies internal JWT signature using public key (RS256 / Ed25519) | |
| | - Executes Role-Based (RBAC) or Attribute-Based (ABAC) authorization | |
| +-----------------------------------------------------------------------------+ |
+-----------------------------------------------------------------------------------+OAuth 2.0 & OpenID Connect (OIDC)#
- OAuth 2.0 (RFC 6749): A delegated authorization framework. It allows third-party clients to obtain limited access to an HTTP resource on behalf of a resource owner without exposing the owner's credentials.
- OpenID Connect (OIDC): An identity layer built directly on top of OAuth 2.0 that provides an
id_token(JWT) communicating who the authenticated user is. - Modern Grant Type Standard:
Authorization Code Grant with PKCE (RFC 7636)*: Mandated for all Single-Page Applications (React, Vue) and native mobile apps to protect against authorization code interception attacks.
Client Credentials Grant*: Used for machine-to-machine (M2M) server-to-server communication without user delegation.
Self-Contained JWTs vs Opaque Reference Tokens#
| Architectural Dimension | Self-Contained JWT (RFC 7519) | Opaque Reference Token |
|---|---|---|
| Payload Storage | Encoded directly inside base64 string | Random cryptographic string (UUID) |
| Validation Speed | Instant local cryptographic verification | Requires network lookup or Redis read |
| Token Size Overhead | Large (500–2,500 bytes on every request) | Tiny (32–64 bytes on the wire) |
| Revocation Capability | Extremely difficult (Valid until expiration) | Instant (Delete key from Redis store) |
| Information Leakage | Payload readable by client via base64 | Zero internal details exposed to client |
The Hybrid Reference Token Pattern (Enterprise Standard)#
To achieve the best of both worlds:
- Public clients are issued a short, secure Opaque Reference Token.
- The client transmits the opaque token to the API Gateway.
- The API Gateway introspects the token once, checks a central Redis revocation list, and mints an ephemeral internal JWT (TTL = 60 seconds).
- The gateway forwards the internal JWT to downstream microservices, which verify it locally via public key cryptography without repeatedly querying Redis or the Identity Provider.
- If a user logs out or is suspended, the gateway revokes the opaque token in Redis instantly.
Checkpoint Practice Problems#
Test your mastery of rate limiting mathematics, gateway architectures, and token security through these detailed problems with complete step-by-step solutions.
Problem 1: Token Bucket Refill Differential Equations & Burst Drain Analysis#
An API gateway enforces a Token Bucket rate limiter on an image transformation endpoint. The bucket parameters are configured as follows:
- Bucket Capacity: C = 50 tokens.
- Continuous Refill Rate: r = 10 tokens/second.
- Cost per request: 1 standard image transformation requires k = 1 token.
- Cost for high-resolution 4K rendering requires k = 5 tokens.
Assume the bucket is completely full (50 tokens) at time t = 0.
A client begins executing a continuous flood of standard requests (k = 1) at an arrival rate of λ_{in} = 30 requests/second.
Calculate:
- The exact duration of time (t_{drain}) until the bucket is completely exhausted of tokens.
- The total number of requests successfully processed during this drain phase (N_{processed}).
- The steady-state throughput of the endpoint after the bucket is exhausted.
- If a client with an empty bucket needs to execute a single 4K transformation (k = 5), calculate the exact waiting time (t_{wait}) the gateway will return in the
Retry-Afterheader.
Solution:
Step 1: Calculate Bucket Drain Time (t_{drain}):
Let T(t) represent the number of tokens in the bucket at time t.
Tokens are consumed at rate:
Consumption Rate = λ_{in} × k = 30 × 1 = 30 tokens/secondTokens refill at rate:
Refill Rate = r = 10 tokens/secondNet rate of token depletion:
(dT) / (dt) = r - λ_{in} × k = 10 - 30 = -20 tokens/secondThe bucket starts at T(0) = C = 50.
Setting T(t_{drain}) = 0:
0 = 50 - 20 × t_{drain}20 × t_{drain} = 50 → t_{drain} = (50) / (20) = 2.5 secondsThe burst capacity sustains the client's 30 RPS demand for exactly 2.5 seconds before exhaustion.
Step 2: Total Requests Processed During Drain Phase:
Requests Processed = λ_{in} × t_{drain} = 30 req/s × 2.5 s = 75 requestsNotice that 75 > 50 because 25 new tokens refilled during the 2.5 seconds of execution (2.5 s × 10 tokens/s = 25 tokens).
Step 3: Steady-State Throughput:
Once the bucket is exhausted (T = 0), tokens become available only as fast as the refill rate:
Steady-State Throughput = r / k = 10 / 1 = 10 requests/secondThe excess incoming requests (30 - 10 = 20 req/sec) are immediately rejected with HTTP 429 Too Many Requests.
Step 4: Retry-After Duration for 4K Transformation (k = 5):
With T = 0 tokens in the bucket, to accumulate k = 5 tokens at refill rate r = 10 tokens/second:
t_{wait} = (k) / (r) = (5 tokens) / (10 tokens/second) = 0.5 seconds = 500 msThe gateway returns:
HTTP/1.1 429 Too Many Requests
Retry-After: 1 (or Retry-After-Ms: 500).
Problem 2: Sliding Window Weighted Counter vs Fixed Window Spike Proof#
A banking API enforces a strict rate limit of L = 600 transactions per minute (W = 60 seconds).
An adversary attempts to overwhelm the payment authorization service by timing requests across window boundaries.
- Prove mathematically the maximum number of requests the adversary can successfully execute within a 10-second rolling interval under a Fixed Window Counter algorithm, and calculate the burst factor over the intended limit.
- Calculate the maximum number of requests the adversary can execute within any rolling 10-second interval under the Sliding Window Weighted Average algorithm.
Solution:
Step 1: Fixed Window Counter Boundary Burst Proof:
Under Fixed Window, time is partitioned into discrete intervals:
Window 1: [0, 60) seconds.
Window 2: [60, 120) seconds.
The adversary crafts the following attack:
- Transmits 0 requests from t = 0 to t = 54.99.
- Transmits 600 requests between t = 55.0 and t = 59.99.
* All 600 requests fall within Window 1 and are permitted.
- At t = 60.0, Window 2 begins. The counter resets to 0.
- Transmits 600 requests between t = 60.0 and t = 65.0.
* All 600 requests fall within Window 2 and are permitted.
Consider the 10-second rolling window from t = 55.0 s to t = 65.0 s:
Total Requests Processed = 600 + 600 = 1,200 transactionsThe intended maximum allowable rate is 600 trans / 60 s = 10 trans/sec.
In the 10-second window, the observed rate was:
Observed Rate = (1,200 transactions) / (10 seconds) = 120 trans/secBurst Factor = 120 / 10 = 12× over the intended sustained rate! Under Fixed Window, an attacker can deliver 2 × L transactions across the boundary edge.
Step 2: Sliding Window Weighted Average Evaluation:
Under Sliding Window Counter, at t = 65.0 s:
- Window 2 has elapsed for 5 seconds (t_{elapsed} = 5 s).
- The previous window (Window 1) count was C_{prev} = 600.
- The current window (Window 2) count is C_{curr}.
The sliding estimate formula evaluates:
Estimated Count = C_{curr} + C_{prev} × ≤ft(1 - (t_{elapsed}) / (W))Estimated Count = C_{curr} + 600 × ≤ft(1 - (5) / (60)) = C_{curr} + 600 × (55) / (60) = C_{curr} + 550To satisfy the rate limit condition:
Estimated Count ≤ 600C_{curr} + 550 ≤ 600 → C_{curr} ≤ 50The maximum number of requests the adversary can successfully execute in Window 2 up to t = 65 s is restricted to 50 transactions.
Total requests executed across the 10-second window (t = 55 to 65):
Total Requests = 600 (from Window 1) + 50 (from Window 2) = 650 transactionsThe sliding window counter algorithm automatically suppressed the boundary burst from 1,200 down to 650, protecting downstream systems from catastrophic spike saturation.
Problem 3: Production Distributed Token Bucket Rate Limiter in Redis & Express#
Implement an enterprise-grade Express.js rate-limiting middleware that connects to a Redis cluster, executes an atomic token bucket Lua script, attaches standard IETF headers, and handles Redis failure gracefully (fail-open mode).
Solution:
// rate-limiter.ts
import { Request, Response, NextFunction } from 'express';
import Redis from 'ioredis';
const LUA_TOKEN_BUCKET = `
local key = KEYS[1]
local capacity = tonumber(ARGV[1])
local fill_rate = tonumber(ARGV[2])
local now = tonumber(ARGV[3])
local requested = tonumber(ARGV[4])
local data = redis.call("HMGET", key, "tokens", "last_update")
local current_tokens = tonumber(data[1])
local last_update = tonumber(data[2])
if current_tokens == nil then
current_tokens = capacity
last_update = now
else
local elapsed = math.max(0, now - last_update)
local generated = elapsed * fill_rate
current_tokens = math.min(capacity, current_tokens + generated)
last_update = now
end
if current_tokens >= requested then
current_tokens = current_tokens - requested
redis.call("HMSET", key, "tokens", current_tokens, "last_update", last_update)
local ttl = math.ceil(capacity / (fill_rate * 1000)) + 60
redis.call("EXPIRE", key, ttl)
return { 1, math.floor(current_tokens), 0 }
else
local needed = requested - current_tokens
local retry_after_ms = math.ceil(needed / fill_rate)
return { 0, math.floor(current_tokens), retry_after_ms }
end
`;
export interface RateLimiterOptions {
capacity: number;
refillTokensPerSecond: number;
cost?: number;
}
export function createTokenBucketMiddleware(redis: Redis, options: RateLimiterOptions) {
const { capacity, refillTokensPerSecond, cost = 1 } = options;
const fillRatePerMs = refillTokensPerSecond / 1000.0;
return async (req: Request, res: Response, next: NextFunction): Promise<void> => {
// Identify client by authenticated User ID, API Key, or Client IP
const clientId = (req as any).user?.id || req.get('X-API-Key') || req.ip || 'anonymous';
const redisKey = `ratelimit:tb:{clientId}`;
const now = Date.now();
try {
const result = (await redis.eval(
LUA_TOKEN_BUCKET,
1,
redisKey,
capacity,
fillRatePerMs,
now,
cost
)) as [number, number, number];
const [isAllowed, remainingTokens, retryAfterMs] = result;
// Attach standard IETF Draft headers
res.setHeader('RateLimit-Limit', capacity.toString());
res.setHeader('RateLimit-Remaining', remainingTokens.toString());
if (isAllowed === 1) {
next();
} else {
const retryAfterSeconds = Math.max(1, Math.ceil(retryAfterMs / 1000));
res.setHeader('Retry-After', retryAfterSeconds.toString());
res.status(429).json({
type: 'https://api.devthesis.com/errors/too-many-requests',
title: 'Too Many Requests',
status: 429,
detail: `Rate limit quota exceeded. Please retry after {retryAfterSeconds} seconds.`,
retryAfterSeconds
});
}
} catch (error) {
// Fail-Open Policy: If Redis is down, log error and permit traffic to avoid global outage
console.error('[RateLimiter] Redis failure encountered. Failing open.', error);
next();
}
};
}Problem 4: Asymmetric Cryptographic JWT Verification Engine#
Construct a self-contained TypeScript module that verifies an asymmetric RS256 / RSA-SHA256 signed JSON Web Token (JWT) using Node's native crypto module without third-party dependencies.
The module must:
- Parse the three-part Base64URL string (
header.payload.signature). - Verify that
alg === "RS256". - Verify the cryptographic digital signature against an RSA Public Key in PEM format.
- Validate standard claims: expiration (
exp), not before (nbf), and issuer (iss).
Solution:
// jwt-verifier.ts
import crypto from 'node:crypto';
export interface JwtPayload {
iss?: string;
sub?: string;
aud?: string;
exp?: number;
nbf?: number;
iat?: number;
roles?: string[];
[key: string]: any;
}
export class NativeJwtVerifier {
private publicKeyPem: string;
private expectedIssuer: string;
constructor(publicKeyPem: string, expectedIssuer: string) {
this.publicKeyPem = publicKeyPem;
this.expectedIssuer = expectedIssuer;
}
private static base64UrlDecode(str: string): string {
let base64 = str.replace(/-/g, '+').replace(/_/g, '/');
while (base64.length % 4 !== 0) {
base64 += '=';
}
return Buffer.from(base64, 'base64').toString('utf-8');
}
public verify(rawToken: string): JwtPayload {
const parts = rawToken.split('.');
if (parts.length !== 3) {
throw new Error('JWT must contain exactly three parts separated by dots');
}
const [headerB64, payloadB64, signatureB64] = parts;
// 1. Decode and parse Header
const headerJson = NativeJwtVerifier.base64UrlDecode(headerB64);
const header = JSON.parse(headerJson);
if (header.alg !== 'RS256') {
throw new Error(`Unsupported JWT algorithm: {header.alg}. Only RS256 is permitted.`);
}
// 2. Cryptographic Signature Verification
const signedData = `{headerB64}.{payloadB64}`;
const signatureBuffer = Buffer.from(
signatureB64.replace(/-/g, '+').replace(/_/g, '/'),
'base64'
);
const isSignatureValid = crypto.verify(
'RSA-SHA256',
Buffer.from(signedData, 'utf-8'),
this.publicKeyPem,
signatureBuffer
);
if (!isSignatureValid) {
throw new Error('Cryptographic signature verification failed: invalid token signature');
}
// 3. Decode and parse Payload
const payloadJson = NativeJwtVerifier.base64UrlDecode(payloadB64);
const payload = JSON.parse(payloadJson) as JwtPayload;
// 4. Validate Standard Claims
const currentUnixTime = Math.floor(Date.now() / 1000);
// Check expiration (exp)
if (typeof payload.exp === 'number' && currentUnixTime >= payload.exp) {
throw new Error(`Token expired at unix timestamp {payload.exp}`);
}
// Check not before (nbf)
if (typeof payload.nbf === 'number' && currentUnixTime < payload.nbf) {
throw new Error(`Token is not valid until unix timestamp {payload.nbf}`);
}
// Check issuer (iss)
if (payload.iss && payload.iss !== this.expectedIssuer) {
throw new Error(`Issuer mismatch: expected {this.expectedIssuer}, got {payload.iss}`);
}
return payload;
}
}Through this architecture, the API Gateway or microservice validates asymmetric JWT signatures with hardware-accelerated OpenSSL primitives, achieving high-throughput, cryptographically sound token verification without external network dependency.