API Security Masterclass: OAuth 2.1, JWT Revocation, Rate Limiting & Webhook Signatures
Lock down your public and internal APIs against credential stuffing, token replay, and replay attacks. Learn modern OAuth 2.1 best practices, cryptographic webhook verification, and distributed sliding-window rate limiting.
1. The Modern API Attack Surface
APIs are the digital nervous system of modern business, powering mobile apps, partner integrations, and external developer platforms. Unfortunately, they are also the primary target for automated credential stuffing, data scraping, token theft, and denial-of-service exploits.
According to cybersecurity telemetry, over 70% of web application breaches originate from insecure API endpoints: missing object-level authorization (BOLA), stale JWT tokens that cannot be revoked, unauthenticated webhooks, and absent rate limiting.
Securing production APIs requires defense-in-depth: from strict cryptographic token validation to distributed sliding-window rate limiting and HMAC-SHA256 webhook signatures.
2. OAuth 2.1 & The Myth of Stateless JWTs
Developers love JSON Web Tokens (JWT) because they are 'stateless': the server validates the signature mathematically without performing a database lookup on every request.
However, purely stateless JWTs introduce a massive security hazard: if a user's token is stolen, or if an administrator bans a malicious account, the token remains valid until its expiration timestamp. Storing sensitive sessions in a 30-day stateless JWT is dangerous.
The modern standard: Short-Lived Access Tokens (5 to 15 minutes) combined with Rotating Refresh Tokens stored in Redis or database tables. When an access token expires, the client exchanges its single-use refresh token. If a refresh token is reused, the auth server detects a token theft attack and immediately invalidates all active sessions for that user.
3. Implementation: Cryptographic HMAC-SHA256 Webhook Verification
When sending or receiving webhooks (such as Stripe payment events or GitHub push notifications), you must cryptographically verify that the payload was generated by the legitimate sender and not tampered with in transit.
Here is the standard, timing-attack-safe HMAC-SHA256 verification function in TypeScript:
import crypto from "crypto";
export function verifyWebhookSignature(
rawBody: string,
signatureHeader: string,
secret: string,
toleranceSeconds: number = 300
): boolean {
// Format: t=1618000000,v1=6a39276d4...
const parts = signatureHeader.split(",");
const timestampPart = parts.find((p) => p.startsWith("t="))?.slice(2);
const signaturePart = parts.find((p) => p.startsWith("v1="))?.slice(3);
if (!timestampPart || !signaturePart) return false;
const timestamp = parseInt(timestampPart, 10);
const now = Math.floor(Date.now() / 1000);
// Prevent Replay Attacks: Reject payloads older than tolerance window
if (Math.abs(now - timestamp) > toleranceSeconds) {
return false;
}
// Compute expected HMAC hash
const signedPayload = `${timestamp}.${rawBody}`;
const expectedSignature = crypto
.createHmac("sha256", secret)
.update(signedPayload, "utf8")
.digest("hex");
// Use timingSafeEqual to prevent side-channel timing attacks
const expectedBuffer = Buffer.from(expectedSignature, "utf8");
const actualBuffer = Buffer.from(signaturePart, "utf8");
if (expectedBuffer.length !== actualBuffer.length) return false;
return crypto.timingSafeEqual(expectedBuffer, actualBuffer);
}4. Distributed Sliding-Window Rate Limiting in Redis
Fixed-window rate limiting (e.g. allowing 100 requests per clock hour) fails because a user can send 100 requests at 11:59 and another 100 requests at 12:00, causing a 200-request spike in 2 seconds.
The Sliding-Window Log algorithm in Redis uses sorted sets (`ZSET`) where timestamps represent scores. When a request arrives, the server removes timestamps older than the window, counts remaining entries, and permits the request only if the count is within limits.
This guarantees smooth, predictable traffic distribution and neutralizes brute-force attacks.
What is BOLA / IDOR in API security?
Broken Object-Level Authorization (BOLA), or Insecure Direct Object References (IDOR), occurs when an API endpoint accepts an ID (e.g. `/api/invoices/992`) without verifying whether the authenticated user owns that invoice.
Why is `timingSafeEqual` necessary in signature checks?
Standard string equality (`a === b`) short-circuits on the first mismatched byte, leaking timing information that allows sophisticated attackers to guess cryptographic signatures byte-by-byte.
What is OAuth 2.1?
OAuth 2.1 consolidates core OAuth 2.0 specifications, mandating PKCE (Proof Key for Code Exchange) for all clients, deprecating the vulnerable Implicit Grant and Resource Owner Password Credentials grant types.