Credential Stuffing in Hapi with Basic Auth
Credential Stuffing in Hapi with Basic Auth — how this specific combination creates or exposes the vulnerability
Credential stuffing in Hapi with Basic Auth occurs because the protocol sends credentials on every request and, when used without TLS, exposes them in transit. Basic Auth encodes user:password with Base64, which is easily reversible, so an attacker intercepting or logging those headers can reuse credentials on other services. In Hapi, routes that rely solely on Basic Auth without additional protections expose the application to automated credential stuffing campaigns where attackers test breached username and password pairs against your endpoints.
Hapi’s built-in auth mechanisms allow developers to implement Basic Auth via auth.strategy('simple', 'basic', { validate: ... }) and apply it to routes using auth: 'simple'. If the validation function performs only a static lookup or compares credentials without rate limiting or anomaly detection, an attacker can send many requests per second using different credential combinations. Because Hapi does not enforce request throttling by default, an unauthenticated attacker can probe the API surface quickly, especially if the API also lacks IP-based rate limiting.
During a black-box scan, middleBrick tests unauthenticated attack surface areas and checks whether authentication can be bypassed or whether weak validation enables credential stuffing. One finding category it reports is BOLA/IDOR and related authentication weaknesses; another is Rate Limiting. When Basic Auth is used without multi-factor controls, adaptive throttling, or secure transport, the risk score degrades because reused credentials from other breaches can gain unauthorized access.
Additional checks include Data Exposure and Encryption. If responses or logs inadvertently echo credentials or if TLS is not enforced, attackers gain further leverage. middleBrick flags these conditions and maps findings to frameworks such as OWASP API Top 10 and PCI-DSS, noting that credential reuse across services amplifies the impact of a single compromised account.
Basic Auth-Specific Remediation in Hapi — concrete code fixes
To harden Hapi against credential stuffing when using Basic Auth, combine transport security, strong validation, and operational protections. Always enforce HTTPS so credentials are not exposed in transit. Use a robust validation function that checks credentials against a secure store, and apply rate limiting to hinder automated attacks. The following examples show a secure Hapi setup with proper Basic Auth, TLS enforcement via a reverse proxy or load balancer configuration (not shown), and rate limiting using a shared store.
Secure Basic Auth strategy with validation and rate limiting
Define an auth strategy that validates credentials against a hashed credential store and integrate it with a rate limiting policy. This reduces the risk of successful credential stuffing by ensuring only known-good credentials are accepted and by throttling excessive attempts.
const Hapi = require('@hapi/hapi');
const bcrypt = require('bcrypt');
const RateLimiter = require('some-rate-limiter'); // e.g., @hapi/basic-auth-rate-limit or custom store
const users = new Map([
// In production, retrieve from a secure DB with hashed passwords
// username -> { passwordHash, scope, ... }
['alice', { passwordHash: '$2b$10$EixZaYVK1fsbw1ZfbX3OXePaWxn96p36WQoeG6Lruj3vjPGga31lW' }] // bcrypt hash for 'correct-horse-battery-staple'
]);
const validate = async (request, username, password, options) => {
const user = users.get(username);
if (!user) {
return { credentials: null, isValid: false };
}
const isValid = await bcrypt.compare(password, user.passwordHash);
return { credentials: { username, scope: user.scope || [] }, isValid };
};
const init = async () => {
const server = Hapi.server({ port: 443, host: '0.0.0.0', tls: { /* cert and key */ } });
// Rate limiter: adjust window and limit to your risk profile
const limiter = new RateLimiter({ windowMs: 60 * 1000, max: 10 }); // 10 requests per minute per identifier
server.auth.strategy('simple', 'basic', { validate });
server.auth.default('simple');
server.route({
method: 'GET',
path: '/secure',
config: {
auth: 'simple',
plugins: {
// Example integration point for custom rate limiting logic
// Implement pre-auth hook to check limiter using request.auth.credentials.username
}
},
handler: async (request, h) => {
const canProceed = await limiter.check(request.auth.credentials.username, request);
if (!canProceed) {
return h.response({ error: 'Too many requests' }).code(429);
}
return { message: 'Access granted', user: request.auth.credentials.username };
}
});
await server.start();
console.log('Server running on %s', server.info.uri);
};
process.on('unhandledRejection', (err) => {
console.error(err);
process.exit(1);
});
init();
Key points in the example:
- TLS termination must be enforced at the edge (load balancer or reverse proxy); Hapi can be configured with TLS options, but in production a dedicated TLS layer is common.
- Passwords are verified using
bcrypt.compare, avoiding plaintext storage or comparison. - Rate limiting is applied per username to slow down credential stuffing attempts; integrate with a shared store if you run multiple server instances.
- Authorization scopes can be attached to credentials to follow least privilege principles.
Additionally, consider pairing Basic Auth with other controls such as MFA for sensitive operations, monitoring for anomalous login patterns, and ensuring that logs do not capture full credentials. middleBrick can detect missing encryption and weak authentication patterns and will surface these findings under Encryption and Rate Limiting categories, along with remediation guidance tied to standards like OWASP API Top 10 and PCI-DSS.