Auth Bypass in Express with Basic Auth
Auth Bypass in Express with Basic Auth — how this specific combination creates or exposes the vulnerability
Basic Authentication in Express sends credentials in an Authorization header as Basic base64(username:password). Because the value is only base64-encoded (not encrypted), any party that can intercept or log the header can recover the credentials. Relying on this transport protection without TLS effectively exposes static credentials to network observers and loggers, which is a prerequisite for many bypass and credential theft scenarios.
Auth bypass occurs when access control checks are incomplete or misapplied rather than missing entirely. In Express applications using Basic Auth, common patterns introduce bypass risks: middleware that checks only whether a user is authenticated but does not re-validate credentials on sensitive routes; route-specific guards that omit protected endpoints; and conditional logic that treats missing or malformed headers as guest access. For example, if middleware sets req.user on successful verification but later routes do not verify req.user, an attacker can reach admin functionality by omitting or manipulating the header. Compounding this, applications that parse credentials per-request but cache authorization decisions without re-checking can allow privilege changes (such as role updates) to go unnoticed on subsequent requests.
Another bypass vector involves handling of preflights and non-GET methods. Basic Auth credentials are not automatically withheld on OPTIONS requests; if the server responds to preflight without enforcing authentication, browsers may proceed with the actual request while exposing the mechanism in CORS headers. Similarly, applications that apply middleware selectively—such as guarding GET /users but not POST /users—create method-level gaps an authenticated low-privilege account can exploit via verb tampering. Even when TLS is used, weak password policies and hardcoded credentials in source code raise the impact of exposure through logs or error messages, enabling credential reuse across services.
Middleware sequencing also matters. If static file serving or error handlers are mounted before auth checks, an attacker may trigger responses that leak stack traces or redirect behavior, aiding further bypass efforts. Because Basic Auth lacks built-in session revocation, compromised credentials remain valid until the password changes or the server-side validation logic is updated. Runtime scans that correlate spec definitions with actual behavior—such as comparing documented required scopes to enforced middleware—can highlight these inconsistencies and point to specific endpoints where authorization does not align with declared security requirements.
Basic Auth-Specific Remediation in Express — concrete code fixes
Secure Basic Auth in Express by enforcing TLS, validating credentials on every request to protected routes, and avoiding conditional or cached authorization decisions. Always serve routes over HTTPS to protect the base64-encoded credentials in transit and prevent trivial recovery from network logs.
Example: Consistent per-route validation
const express = require('express');
const app = express();
const port = 3000;
const validUsers = new Map([
['admin', '$2a$10$abc123hashForAdmin'], // store bcrypt hashes, not plain passwords
['readonly', '$2a$10$def456hashForReadonly']
]);
const basicAuth = (req, res, next) => {
const header = req.headers.authorization;
if (!header || !header.startsWith('Basic ')) {
res.set('WWW-Authenticate', 'Basic realm="API"');
return res.status(401).send('Authentication required');
}
const decoded = Buffer.from(header.slice(6), 'base64').toString('utf-8');
const [username, password] = decoded.split(':');
if (!username || !password) {
return res.status(400).send('Invalid authorization header');
}
const hashed = validUsers.get(username);
if (!hashed) {
return res.status(403).send('Access denied');
}
// TODO: use a proper password compare (bcrypt, argon2)
const isValid = mockCompare(password, hashed);
if (!isValid) {
return res.status(403).send('Invalid credentials');
}
req.user = { username, role: username === 'admin' ? 'admin' : 'readonly' };
return next();
};
// Apply to all admin routes
app.use('/admin', basicAuth, (req, res, next) => {
if (req.user.role !== 'admin') {
return res.status(403).send('Insufficient privileges');
}
next();
});
app.get('/admin/users', basicAuth, (req, res) => {
res.json({ users: ['alice', 'bob'] });
});
app.listen(port, () => console.log(`Server listening on port ${port}`));
function mockCompare(input, storedHash) {
// Replace with bcrypt.compare or similar in production
return storedHash === '$2a$10$abc123hashForAdmin' && input === 'password123';
}
Example: Applying authentication selectively with method awareness
app.options('*', (req, res) => {
res.set('Allow', 'GET, POST, PUT, DELETE');
res.sendStatus(200);
});
// Apply auth to both GET and POST under /users
app.route('/users')
.all(basicAuth)
.get((req, res) => {
res.json({ users: ['alice'] });
})
.post((req, res) => {
if (req.user.role !== 'admin') {
return res.status(403).send('Create not allowed');
}
res.status(201).send('Created');
});
Operational practices
- Never store passwords in plain text or in source code; use adaptive hashing (bcrypt, argon2) for verification.
- Always enforce HTTPS in production to prevent credential exposure in transit and logs.
- Avoid caching authorization decisions across requests; re-validate on each sensitive operation.
- Apply middleware consistently across all routes and methods, including error handlers and static assets.
- Use standardized WWW-Authenticate challenges and return 401 for missing credentials, 403 for invalid credentials.
Related CWEs: authentication
| CWE ID | Name | Severity |
|---|---|---|
| CWE-287 | Improper Authentication | CRITICAL |
| CWE-306 | Missing Authentication for Critical Function | CRITICAL |
| CWE-307 | Brute Force | HIGH |
| CWE-308 | Single-Factor Authentication | MEDIUM |
| CWE-309 | Use of Password System for Primary Authentication | MEDIUM |
| CWE-347 | Improper Verification of Cryptographic Signature | HIGH |
| CWE-384 | Session Fixation | HIGH |
| CWE-521 | Weak Password Requirements | MEDIUM |
| CWE-613 | Insufficient Session Expiration | MEDIUM |
| CWE-640 | Weak Password Recovery | HIGH |