Api Rate Abuse in Sails with Basic Auth
Api Rate Abuse in Sails with Basic Auth — how this specific combination creates or exposes the vulnerability
Rate abuse in a Sails application that uses HTTP Basic Authentication can occur when authentication checks are performed before or in parallel with rate-limiting logic, or when rate limiting is not applied at all. In Sails, controllers typically handle incoming requests and may first validate credentials (e.g., via an authentication policy) before executing business logic. If rate limiting is implemented only at a later stage, or not applied to the authentication endpoint itself, an attacker can exhaust authentication attempts or downstream resources without effective throttling.
Basic Auth transmits credentials with each request using an Authorization header of the form Authorization: Basic base64(username:password). Because the credentials are sent on every request, they can be used as identifiers for rate-limiting; however, if the application does not tie rate limits to the identity derived from Basic Auth, an attacker can rotate credentials or hammer the endpoint anonymously. Sails policies run before controller actions, so if a policy performs authentication but does not enforce rate limits, the action may execute unchecked after a successful login. This is especially risky for password reset, login, or token issuance endpoints, where attackers can perform credential spraying or brute-force attacks at scale.
For example, consider a Sails controller that authenticates via Basic Auth and issues a session-like token without any per-user rate constraints:
// api/controllers/AuthController.js
module.exports = {
login: async function (req, res) {
const { username, password } = req.allParams();
const user = await User.findOne({ username });
if (!user || user.password !== hashPassword(password)) {
return res.unauthorized('Invalid credentials');
}
// No rate limiting applied here
const token = AuthService.createToken(user);
return res.ok({ token });
}
};
In this scenario, an attacker can repeatedly call POST /auth/login with different credentials, potentially triggering credential stuffing or brute-force attacks. Without explicit rate limiting tied to either the client IP or the user identity extracted from the Basic Auth header, the attack surface remains wide. Even if the application uses IP-based throttling, rotating IPs or using botnets can bypass these protections. The combination of per-request Basic Auth transmission and missing or weak rate limiting enables high-volume abuse that can degrade service availability and expose authentication mechanisms.
To detect such issues, scans evaluate whether rate limiting is applied to authentication and sensitive endpoints, and whether limits are scoped to identifiers derived from Basic Auth or other request attributes. Findings highlight missing throttling on endpoints accepting Basic Auth credentials and recommend binding rate limits to authenticated identities or IPs to reduce abuse risk.
Basic Auth-Specific Remediation in Sails — concrete code fixes
To mitigate rate abuse with Basic Auth in Sails, enforce rate limits that incorporate credentials or derived identifiers, and ensure limits are applied before authentication-sensitive logic consumes resources. Use a combination of IP-based and credential-based throttling where feasible, and avoid leaking information about valid usernames through timing differences or error messages.
Example: Apply rate limiting per username extracted from the Basic Auth header within a Sails policy:
// api/policies/rate-limit-basic-auth.js
const rateLimit = require('rate-limiter-flexible');
module.exports = async function rateLimitBasicAuth(req, res, proceed) {
const authHeader = req.headers('authorization');
if (authHeader && authHeader.startsWith('Basic ')) {
const base64 = authHeader.split(' ')[1];
const decoded = Buffer.from(base64, 'base64').toString('utf-8');
const [username] = decoded.split(':');
if (username) {
const limiter = new rateLimit.RateLimiterMemory({
points: 10, // 10 requests
duration: 60 // per 60 seconds
});
try {
await limiter.consume(username); // consume per username
return proceed();
} catch (rej) {
return res.status(429).json({ error: 'Too Many Requests' });
}
}
}
// Fallback to IP-based limiting if no Basic Auth present
const ipLimiter = new rateLimit.RateLimiterMemory({
points: 100,
duration: 60
});
try {
await ipLimiter.consume(req.ip);
return proceed();
} catch {
return res.status(429).json({ error: 'Too Many Requests' });
}
};
Example: Apply the policy globally or to specific controllers in config/policies.js:
module.exports.policies = {
AuthController: {
'*': ['rateLimitBasicAuth']
},
'*': {
'*': true // apply other policies as needed
}
};
These configurations ensure that each username derived from Basic Auth is subject to its own rate bucket, reducing the effectiveness of credential rotation attacks. For production, consider a shared store (e.g., Redis via rate-limiter-flexible adapters) to synchronize limits across multiple Sails instances. Always pair rate limiting with secure password storage and consider additional protections such as account lockout after repeated failures, while being mindful of denial-of-service risks introduced by lockout mechanisms.