Back to Blog
critical SEVERITY6 min read

How Rate Limiting Vulnerabilities Happen in Node.js OAuth Endpoints and How to Fix Them

A critical resource exhaustion vulnerability was discovered in the OAuth token endpoint at `server/routes/oauth.js`. Without rate limiting, attackers could flood the `/api/oauth/token` endpoint with requests, each triggering expensive bcrypt verification operations that would exhaust server CPU and memory. The fix implements per-IP rate limiting using `express-rate-limit` to cap requests at 20 per 15-minute window.

O
By Orbis AppSec
•Technically reviewed by Anupam Mediratta•Published September 4, 2026•Reviewed September 4, 2026

Answer Summary

This is a resource exhaustion vulnerability (CWE-770) in a Node.js Express OAuth endpoint where the `/api/oauth/token` route lacked rate limiting. Each authentication request triggers expensive bcrypt operations, allowing attackers to exhaust server resources through request flooding. The fix adds `express-rate-limit` middleware configured to allow only 20 requests per IP address within a 15-minute window, returning HTTP 429 when exceeded.

Vulnerability at a Glance

cweCWE-770 (Allocation of Resources Without Limits or Throttling)
fixAdded express-rate-limit middleware with 20 requests per 15-minute window per IP
riskDenial of Service through CPU/memory exhaustion
languageJavaScript (Node.js/Express)
root causeOAuth token endpoint accepts unlimited requests triggering expensive bcrypt operations
vulnerabilityResource Exhaustion via Missing Rate Limiting

Introduction

The /api/oauth/token endpoint in server/routes/oauth.js handles one of the most security-critical operations in any application: exchanging credentials for access tokens. However, a dangerous oversight at line 140 left this endpoint vulnerable to a resource exhaustion attack that could bring down the entire server.

The problem wasn't with the authentication logic itself—input sanitization was already in place. The issue was that every single request to this endpoint triggers an expensive bcrypt verification operation, and there was absolutely nothing stopping an attacker from sending thousands of requests per second.

// Before the fix - no rate limiting on this expensive endpoint
app.post(buildServerPath('/api/oauth/token'), async (req, res) => {
  // Each request triggers bcrypt.compare() - extremely CPU intensive

For developers building OAuth implementations or any authentication system, this vulnerability demonstrates why defense-in-depth matters: even well-validated inputs can become attack vectors when resource consumption isn't controlled.

The Vulnerability Explained

Why bcrypt Makes This Dangerous

The OAuth token endpoint uses bcrypt to verify client secrets. Bcrypt is intentionally slow—that's its security feature. Each verification operation is designed to take significant CPU time to prevent offline brute-force attacks. But this same property becomes a weapon when attackers can trigger unlimited verifications.

Here's what the vulnerable code path looked like:

app.post(buildServerPath('/api/oauth/token'), async (req, res) => {
  try {
    const platform = configCache.getPlatform() || {};
    const oauthConfig = platform.oauth || {};
    // ... bcrypt verification happens here for every request

Attack Scenario

An attacker targeting this specific endpoint could execute a straightforward denial-of-service attack:

  1. Identify the endpoint: The /api/oauth/token path is publicly accessible and follows standard OAuth conventions
  2. Craft minimal requests: Send POST requests with any grant_type, username, and password values
  3. Flood the server: Launch parallel requests from multiple IPs or a single source
  4. Exhaust resources: Each request forces the server to execute bcrypt operations, consuming CPU cycles

With just 50 concurrent requests (as tested in the regression suite), the server would be spending all its CPU time on bcrypt operations, leaving legitimate users unable to authenticate or access the application.

Real-World Impact

For this Node.js application, the impact is severe:

  • Complete service disruption: The single-threaded event loop becomes blocked by CPU-intensive bcrypt operations
  • No authentication possible: Legitimate users cannot obtain tokens
  • Cascading failures: Other routes sharing the same process become unresponsive
  • Low attack cost: The attacker needs minimal resources compared to the damage inflicted

The Fix

The fix introduces express-rate-limit middleware specifically configured for the OAuth token endpoint:

Before (Vulnerable)

app.post(buildServerPath('/api/oauth/token'), async (req, res) => {

After (Protected)

import rateLimit from 'express-rate-limit';

// Mitigates brute-force / resource-exhaustion attacks: each request to the
// token endpoint triggers an expensive bcrypt client-secret comparison, so an
// unauthenticated flood can exhaust CPU. Limit per-IP independently of any
// broader shared limiter.
const oauthTokenLimiter = rateLimit({
  windowMs: 15 * 60 * 1000,  // 15 minute window
  limit: 20,                   // 20 requests per window
  standardHeaders: true,       // Return rate limit info in headers
  legacyHeaders: false,        // Disable X-RateLimit-* headers
  message: { error: 'Too many token requests from this IP, please try again later.' }
});

app.post(buildServerPath('/api/oauth/token'), oauthTokenLimiter, async (req, res) => {

Why These Specific Settings?

The configuration choices are deliberate:

Setting Value Rationale
windowMs 15 minutes Balances security with legitimate retry scenarios
limit 20 requests Allows normal OAuth flows while blocking floods
standardHeaders true Enables RateLimit-* headers per RFC draft
legacyHeaders false Avoids deprecated header format

The middleware is applied only to the token endpoint, not globally. This ensures that:
- Other routes aren't unnecessarily restricted
- The expensive bcrypt operations are specifically protected
- Rate limit state is isolated to this security-critical path

Key Security Improvement

When the rate limit is exceeded, the server immediately returns HTTP 429 (Too Many Requests) without executing the bcrypt operation. This is crucial—the expensive work is avoided entirely for excess requests, preserving CPU for legitimate traffic.

Key Takeaways

  • The /api/oauth/token endpoint's bcrypt operations made it a prime DoS target — any publicly accessible endpoint with expensive operations needs rate limiting
  • Rate limiting must be applied before the expensive operation — returning 429 early prevents resource consumption, not just abuse detection
  • Per-endpoint rate limiters provide granular control — the oauthTokenLimiter is independent of any global limiter, ensuring appropriate limits for this specific threat
  • Input validation is not resource protection — this endpoint had proper input sanitization but still needed rate limiting to prevent abuse
  • The 20 requests per 15 minutes limit balances security and usability — legitimate OAuth flows rarely need more, while attacks require thousands

How Orbis AppSec Detected This

  • Source: Unauthenticated HTTP POST requests to /api/oauth/token
  • Sink: bcrypt.compare() operation triggered for every request in server/routes/oauth.js:140
  • Missing control: No rate limiting middleware protecting the expensive authentication operation
  • CWE: CWE-770 (Allocation of Resources Without Limits or Throttling)
  • Fix: Added express-rate-limit middleware configured for 20 requests per 15-minute window per IP address

Orbis AppSec automatically detected this vulnerability and opened a pull request with the fix. Try Orbis AppSec on your repositories to find and fix issues like this automatically.

Conclusion

This vulnerability in server/routes/oauth.js illustrates a critical principle: security isn't just about validating inputs—it's about controlling resource consumption. The bcrypt algorithm's intentional slowness, designed to protect against offline attacks, became a liability when exposed to unlimited online requests.

The fix is elegantly simple: a rate limiter that stops abuse before the expensive operation executes. For any developer building authentication systems, this pattern should be standard practice. Identify your expensive operations, protect them with appropriate rate limits, and test that those limits actually work under load.

Prevention and further reading

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #2265

Related Articles

critical

Music Studio App Express Server: CWE-287 Authentication Bypass via

A critical authentication bypass vulnerability in a music studio application's Express server allowed remote attackers to access API endpoints despite the intended localhost-only restriction. The localRequest middleware validated the Host header but failed to verify the actual remote socket address, enabling trivial bypasses in containerized and multi-user environments.

high

CVE-2026-104850: MCP TypeScript SDK OAuth Credential Leak

The MCP TypeScript SDK (`@modelcontextprotocol/sdk`) contained a flaw in its OAuth client flow where the MCP server being connected to could influence which authorization server the client talked to, meaning client credentials and token-exchange traffic could be directed to an endpoint the attacker controls. This project was pinned to the affected `1.30.0`; the dependency has been moved to `^1.31.0`, which carries the upstream fix for CVE-2026-104850. Any MCP client that performs OAuth against t

high

SQLite Auth Database Plaintext Storage in InitAuthDB()

The `InitAuthDB()` function opened SQLite databases without restricting filesystem permissions, leaving bcrypt password hashes, session tokens, and user settings exposed to any local account with read access. The fix explicitly sets `os.Chmod(filepath, 0600)` immediately after database creation, ensuring only the owner can access authentication secrets.

high

undici 8.10.0 Cache Poisoning: CVE-2026-85152 Auth Bypass

undici, the HTTP client used by Node.js's `fetch()` implementation, shipped a caching layer that did not properly isolate cached responses by origin, letting a response poisoned on one origin be served to requests for another. This created a path to cross-origin authentication bypass, tracked as CVE-2026-85152 and fixed in undici 8.10.2.

critical

Hybridauth Telegram OAuth Timing Attack in `authenticateCheckError()`

The Telegram OAuth provider in Hybridauth relied on `strcmp()` to validate HMAC-SHA256 signatures, exposing a timing side-channel that could allow attackers to forge authentication tokens character by character. The fix replaces this with PHP's timing-safe `hash_equals()` function, eliminating the information leak.

critical

Node.js Auth Query SQL Injection via Group Code Interpolation

A sign-in authorization check built its SQL query by mapping an `authorizedGroups` array into quoted string literals and joining them directly into a template literal, creating a classic SQL injection point in a critical authentication path. The fix replaces every interpolated value — including the previously "typed" parameter — with `?` placeholders bound through a value-builder helper, closing off the injection vector entirely.