Brute Force Attack in Adonisjs with Dynamodb
Brute Force Attack in Adonisjs with Dynamodb — how this specific combination creates or exposes the vulnerability
A brute force attack against an AdonisJS application using DynamoDB typically centers on the authentication layer. When login routes are not rate-limited or do not employ adaptive defenses, an attacker can submit many credential guesses against the endpoint. Because AdonisJS often uses DynamoDB as a user store, the pattern of requests interacts with how identities are looked up and how verification is performed.
In AdonisJS, authentication commonly relies on the auth module and provider configuration that points to a DynamoDB model. If the route handling login does not enforce per-identifier or per-IP rate limits, each guess results in a DynamoDB query (e.g., get or query) to retrieve the user record by username or email. DynamoDB charges and performance characteristics can make high-volume scans expensive, but the immediate risk is not cost — it is the absence of throttling that allows rapid guessing.
Consider a typical AdonisJS LoginController:
async login({ request, auth }) {
const { email, password } = request.only(['email', 'password'])
const user = await User.findBy('email', email)
if (user && (await verifyPassword(password, user.password))) {
return auth.attempt(user.id, password)
}
throw new BadException('Invalid credentials', 401, 'E_INVALID_CREDENTIALS')
}
This pattern performs a DynamoDB lookup for every login attempt, and if there is no rate limiting, an attacker can iterate over many emails or usernames. Even if the application returns a generic error message, timing differences or response behavior can leak whether a specific identifier exists. The issue is not DynamoDB itself but the combination of an unprotected route, a predictable lookup pattern, and a user store that does not impose request-side limits.
Additionally, if the application exposes account enumeration through different error paths (e.g., "user not found" vs "incorrect password"), an attacker can further refine guesses. Without rate limiting or other adaptive controls, the attack surface is widened. This aligns with the broader category of Broken Object Level Authorization (BOLA) and brute force patterns, where weak access controls around identity enable abuse.
To detect such issues, middleBrick runs checks for Rate Limiting alongside Authentication and BOLA/IDOR. It examines the API specification and runtime behavior to identify whether login paths are guarded by sufficient request throttling and whether responses avoid revealing account existence.
Dynamodb-Specific Remediation in Adonisjs — concrete code fixes
Mitigating brute force risk in AdonisJS with DynamoDB requires changes at the route and data-access layers. The goal is to ensure that every login attempt incurs a consistent cost and that aggressive requests are throttled regardless of identifier validity.
1. Constant-time identifier handling
Avoid branching on user existence before verification. Instead, use a fixed flow that always performs a comparable amount of work. With DynamoDB, you can still fetch a record by a known key, but you should ensure the response handling does not leak information. If you must guard against enumeration, consider using a stable error path and a synthetic delay when a user is not found.
async login({ request, auth }) {
const { email, password } = request.only(['email', 'password'])
// Fetch user by a normalized key; this operation should be constant-time where possible
const user = await User.findBy('email', email)
// Always verify password with a constant-time comparison when user exists
const hasUser = !!user
const hasValidPassword = hasUser && await verifyPassword(password, user.password)
// Use a generic delay to reduce timing differences when user does not exist
if (!hasValidPassword) {
if (!hasUser) {
await new Promise((res) => setTimeout(res, 500)) // synthetic delay
}
throw new BadException('Invalid credentials', 401, 'E_INVALID_CREDENTIALS')
}
return auth.attempt(user.id, password)
}
2. Per-identifier and per-IP rate limiting
Use a sliding window or token-bucket approach to limit attempts for a given email and originating IP. In AdonisJS, you can integrate a rate limiter middleware that tracks identifiers and IPs against a DynamoDB-backed store or an in-memory store for short windows. Below is an example using a simple in-memory map for prototyping; for production, consider a distributed store coordinated with DynamoDB streams or an external service.
// middleware/rateLimitLogin.js
const attempts = new Map()
function rateLimitLogin(email, ip, limit = 5, windowMs = 15 * 60 * 1000) {
const key = `${email}:${ip}`
const now = Date.now()
const record = attempts.get(key) || { count: 0, first: now, last: now }
if (now - record.first > windowMs) {
record.count = 1
record.first = now
} else {
record.count += 1
}
record.last = now
attempts.set(key, record)
return record.count > limit
}
module.exports = (email, ip) => rateLimitLogin(email, ip)
// In LoginController
const isRateLimited = require('../middleware/rateLimitLogin')(email, request.ip())
if (isRateLimited) {
throw new BadException('Too many attempts', 429, 'E_RATE_LIMIT')
}
3. DynamoDB conditional writes for lockouts
To persist rate-limiting state across restarts and instances, store attempt counts in DynamoDB with a TTL attribute. This example uses the AWS SDK for JavaScript to update an item atomically:
const { DynamoDBDocumentClient, UpdateCommand } = require('@aws-sdk/lib-dynamodb')
const ddbClient = DynamoDBDocumentClient.from(new DynamoDB({ region: 'us-east-1' }))
async function recordAttempt(email, ip) {
const key = { pk: `login#${email}`, sk: `ip#${ip}` }
const cmd = new UpdateCommand({
TableName: process.env.AUTH_LOCKOUT_TABLE,
Key: key,
UpdateExpression: 'SET attempts = if_not_exists(attempts, :zero) + :inc, lastAttempt = :now',
ConditionExpression: 'attribute_not_exists(pk) OR lastAttempt < :cutoff',
ExpressionAttributeValues: {
':inc': 1,
':now': Date.now(),
':zero': 0,
':cutoff': Date.now() - 15 * 60 * 1000
},
ReturnValues: 'UPDATED_NEW'
})
try {
await ddbClient.send(cmd)
} catch (err) {
if (err.name !== 'ConditionalCheckFailedException') throw err
// Lockout already enforced
return true
}
return false
}
By combining constant-time flows, robust rate limiting, and DynamoDB-backed persistence, you reduce the effectiveness of brute force attempts against AdonisJS endpoints.
middleBrick scans can surface missing rate limits and weak authentication patterns by correlating authentication configurations with runtime behavior. The platform also supports findings mapped to frameworks such as OWASP API Top 10 and compliance regimes like SOC2 and GDPR.