Broken Authentication
OWASP API #2. API keys in URLs, missing credential rotation, no rate limit on login, predictable tokens, JWT flaws specific to API contexts (no HttpOnly, no CSRF as defence, token in query string).
OWASP API #2. API keys in URLs, missing credential rotation, no rate limit on login, predictable tokens, JWT flaws specific to API contexts (no HttpOnly, no CSRF as defence, token in query string).
Broken Authentication is the second most critical API security risk according to OWASP. APIs face unique authentication challenges that web applications do not: tokens are sent in every request (often in URLs or headers that get logged), there is no built-in CSRF protection, and authentication endpoints must be aggressively rate-limited to prevent credential stuffing and brute-force attacks.
Browser-based web applications have built-in security mechanisms that APIs lack. CSRF tokens protect against cross-origin requests. HttpOnly cookies keep session tokens out of JavaScript reach. The browser enforces same-origin policy. APIs receive none of these protections automatically. Tokens are transmitted in Authorization headers or even URL query strings, which are logged by proxies, load balancers, and CDNs. A single log entry can expose a valid session token to anyone with access to the infrastructure logs.
Beyond token storage, APIs expose authentication endpoints — login, logout, token refresh, password reset — that are trivially automated. Without rate limiting, an attacker can send millions of login attempts per hour. Without short token expiry, a stolen token remains valid for days or weeks. Without proper JWT signing, an attacker can forge tokens by changing the algorithm from RS256 to HS256 using a known public key.
The simulator below lets you test three authentication scenarios: token leaked in a URL query string, rate-limited login endpoint, and proper authentication with the token in an Authorization header. Watch the request and response logs to see how each scenario behaves.
Test three authentication scenarios and observe the differences in request behaviour.
No requests yet.
In 2021, security researchers disclosed that the Peloton API had multiple broken authentication flaws. The most critical was that the GET /api/user/me endpoint returned the authenticated user's data without requiring a valid session token for all request methods. Additionally, the API had no rate limiting on authentication endpoints, enabling credential stuffing attacks against user accounts. Peloton fixed the issue by enforcing session validation on all endpoints and introducing rate limiting on login and token-refresh routes.
The Peloton case illustrates a common pattern: developers assume that certain endpoints are "safe" because they return data about the current user, but they forget to verify the session for every HTTP method or edge case. Combined with missing rate limiting, a single auth gap exposes every user account to compromise.
Fixing broken authentication requires a layered approach. Rate limit every authentication endpoint — login, logout, token refresh, password reset — to prevent brute-force and credential stuffing attacks. Never transmit tokens in URL query strings or request bodies that get logged. Use short-lived JWTs (15-30 minutes for access tokens) with refresh tokens stored in HttpOnly cookies. Sign JWTs with a strong, secret key and reject the "none" algorithm.
// VULNERABLE — token in URL query string
const loginUrl = `https://api.example.com/auth/login?token=${jwt}`;
// Logged by: CDN, load balancer, reverse proxy, browser history
// VULNERABLE — no rate limiting on login
app.post('/api/auth/login', async (req, res) => {
const user = await db.user.findUnique({ where: { email: req.body.email } });
if (!user || !bcrypt.compare(req.body.password, user.passwordHash)) {
return res.status(401).json({ error: 'Invalid credentials' });
}
const token = jwt.sign({ userId: user.id }, SECRET, { expiresIn: '7d' });
res.json({ token });
// No rate limit — attacker can try 10,000 passwords/second
});
// SAFE — rate limited, token in header, short expiry
const rateLimiter = rateLimit({ windowMs: 60_000, max: 5 });
app.post('/api/auth/login', rateLimiter, async (req, res) => {
const user = await db.user.findUnique({ where: { email: req.body.email } });
if (!user || !bcrypt.compare(req.body.password, user.passwordHash)) {
return res.status(401).json({ error: 'Invalid credentials' });
}
const token = jwt.sign({ userId: user.id }, SECRET, { expiresIn: '15m' });
res.json({ token }); // Client sends in Authorization: Bearer <token>
});Additional measures: implement MFA for sensitive operations, rotate refresh tokens on each use, store password hashes with a strong algorithm (argon2 or bcrypt), and monitor authentication endpoints for anomalous traffic patterns.
1.Why is API authentication fundamentally different from browser-based web app authentication?
2.An API returns the JWT token in the URL query string after login. What is the primary risk?
3.What is the purpose of rate limiting on authentication endpoints?