Password Spraying in Adonisjs with Api Keys
Password Spraying in Adonisjs with Api Keys — how this specific combination creates or exposes the vulnerability
Password spraying is an authentication attack where an adversary uses a small list of common passwords against many accounts to avoid account lockouts. When an AdonisJS application relies on API keys for authentication but does not enforce rate limits or tie API key validation to a per-user failure context, password spraying can be indirectly enabled.
In AdonisJS, API keys are often used to identify services or integrations rather than human users. If the key validation logic does not track or isolate failed attempts by the associated user identity, an attacker who obtains a valid API key prefix or a known key pattern can iterate passwords across multiple accounts without triggering per-account lockouts. The risk is compounded when the login or key validation endpoint does not enforce global rate limits, allowing rapid, low-volume requests that evade simple per-IP throttling.
Consider an AdonisJS route that first identifies a user by an API key and then validates a password for a secondary operation (for example, a privileged action or a token exchange). If the route does not enforce consistent delays and error messages regardless of whether the API key is valid, an attacker can enumerate valid API keys and then perform password spraying against the associated user accounts. The framework’s default session and authentication guards may not apply the same protections to API-key-based flows as they do to form-based login, creating an implicit bypass path.
Real-world attack patterns such as credential stuffing and OWASP API Top 10 #7 (Identification and Authentication Failures) apply here. Without correlating API key usage with per-user failure tracking and global rate limiting, an attacker can chain a leaked or guessed API key with password spraying to escalate access across accounts. This maps to insecure authentication designs where protections are siloed by authentication method rather than by identity.
Api Keys-Specific Remediation in Adonisjs — concrete code fixes
To mitigate password spraying in AdonisJS when API keys are involved, enforce per-identity rate limiting, consistent error handling, and strict validation boundaries. Below are concrete code examples that demonstrate how to implement these protections.
Consistent authentication response behavior
Ensure that authentication responses do not reveal whether an API key is valid. Use a fixed delay and a generic message regardless of key validity.
// start/hooks.ts
import { Exception } from '@poppinss/utils'
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
export const authenticationHook = async (ctx: HttpContextContract, next: () => Promise) => {
const apiKey = ctx.request.header('X-API-Key')
if (!apiKey) {
return ctx.response.unauthorized({ message: 'Authentication required' })
}
// Simulate key lookup with constant-time behavior
const keyIsValid = await validateApiKeyConsistent(apiKey)
if (!keyIsValid) {
// Always apply delay to prevent timing-based discrimination
await new Promise((resolve) => setTimeout(resolve, 500))
return ctx.response.unauthorized({ message: 'Authentication required' })
}
await next()
}
async function validateApiKeyConsistent(apiKey: string): Promise {
// Use a constant-time comparison where possible
const normalized = apiKey.trim()
// Example: fetch key record and compare using safe methods
// Avoid early exits that leak validity
const record = await ApiKey.findBy('key_hash', hash(normalized))
return !!record
}
Per-user rate limiting tied to API key identity
Track failed attempts by the identity associated with the API key and enforce global rate limits.
// start/helpers/rateLimit.ts
import { RateLimiterRedis } from 'rate-limiter-flexible'
import { Redis } from '@ioc:Adonis/Addons/Redis'
const limiter = new RateLimiterRedis({
storeClient: Redis.connection,
keyPrefix: 'api_key_rl',
points: 10, // 10 requests
duration: 60, // per 60 seconds
blockDuration: 300, // block for 5 minutes if exceeded
})
export const applyRateLimitByKey = async (apiKey: string): Promise => {
try {
await limiter.consume(apiKey)
return true
} catch (error) {
return false
}
}
Use this helper in your route or middleware to enforce request quotas per API key identity, reducing the feasibility of password spraying across accounts.
Route protection with combined API key and password checks
When an API key is used to identify a user context before password-sensitive operations, enforce additional guards.
// routes/auth.ts
import Route from '@ioc:Adonis/Core/Route'
import { applyRateLimitByKey } from 'App/Helpers/rateLimit'
Route.post('/privileged-action', async ({ request, response }) => {
const apiKey = request.header('X-API-Key')
const { password } = request.body()
const allowed = await applyRateLimitByKey(apiKey)
if (!allowed) {
return response.status(429).json({ message: 'Too many requests' })
}
// Validate password with constant-time checks where applicable
const user = await getUserByKey(apiKey)
const passwordValid = await verifyPassword(user, password)
if (!passwordValid) {
await incrementFailureCount(user.id)
return response.unauthorized({ message: 'Invalid credentials' })
}
return response.ok({ message: 'Action authorized' })
})
Middleware to correlate API keys with user identities for monitoring
Log and monitor authentication outcomes without leaking validity details, enabling detection of spraying patterns.
// start/middleware/auth_logger.ts
import { Exception } from '@poppinss/utils'
export const authLogger = async (context, next) => {
const start = Date.now()
await next()
const duration = Date.now() - start
// Log key ID (not the key itself), outcome, and timing metadata for SIEM integration
const apiKey = context.request.header('X-API-Key')
const keyId = apiKey ? hash(apiKey).slice(0, 8) : 'none'
const outcome = context.response.status === 401 ? 'unauthorized' : 'ok'
console.log(JSON.stringify({ event: 'auth_attempt', keyId, outcome, duration }))
}
By combining consistent validation timing, per-identity rate limiting, and careful logging, you reduce the risk of password spraying against accounts linked to API keys in AdonisJS.