Broken Authentication in Hapi
How Broken Authentication Manifests in Hapi
Broken authentication in Hapi applications typically emerges from misconfigured authentication strategies, improper session handling, and flawed token validation. Hapi's plugin-based architecture, while powerful, creates multiple attack surfaces where authentication can be bypassed or weakened.
The most common vulnerability occurs when developers register authentication strategies but fail to apply them consistently across routes. A typical pattern shows authentication configured at the server level but selectively disabled on sensitive endpoints:
const server = Hapi.server({ port: 3000 });
server.auth.strategy('jwt', 'jwt', {
key: process.env.JWT_SECRET,
validate: validateJWT,
verifyOptions: { algorithms: ['HS256'] }
});
server.auth.default('jwt');
// Vulnerable route - authentication bypassed
server.route({
method: 'GET',
path: '/admin/users',
options: {
auth: false // Critical security gap
},
handler: (request, h) => {
return getAllUsers();
}
});Another frequent issue involves improper scope validation. Hapi's scope system checks for permissions, but developers often implement weak validation logic:
const handler = (request, h) => {
const user = request.auth.credentials;
// Flawed scope check - allows privilege escalation
if (user.scope.includes('admin')) {
return getAllUserData();
}
return getUserData(user.id);
};This code fails to verify that the authenticated user actually owns the data they're requesting. An attacker with 'admin' scope could access any user's data regardless of ownership.
Session fixation attacks target Hapi's cookie-based authentication when developers don't regenerate session identifiers after privilege changes:
const loginHandler = async (request, h) => {
const { username, password } = request.payload;
const user = await authenticateUser(username, password);
// Vulnerable - session ID not regenerated
request.cookieAuth.set(user);
return h.redirect('/dashboard');
};Timing attacks exploit Hapi's authentication validation functions when they use non-constant time comparisons:
const validateJWT = async (artifacts, request) => {
const isValid = artifacts.payload.sub === 'expected-user';
// Vulnerable to timing attacks
if (isValid) {
return { isValid: true };
}
return { isValid: false };
};Credential stuffing becomes effective when Hapi applications lack rate limiting on authentication endpoints:
server.route({
method: 'POST',
path: '/login',
options: {
auth: false
},
handler: async (request, h) => {
// No rate limiting - vulnerable to credential stuffing
const { username, password } = request.payload;
const user = await findUser(username);
if (user && await verifyPassword(password, user.hash)) {
return { success: true };
}
return { success: false };
}
});Hapi-Specific Detection
Detecting broken authentication in Hapi requires examining both configuration files and runtime behavior. The middleBrick API security scanner provides comprehensive coverage of Hapi-specific authentication vulnerabilities through its black-box scanning approach.
middleBrick's authentication module tests for several Hapi-specific patterns:
- Authentication strategy bypass attempts on routes with
auth: falseor missing authentication - Scope validation weaknesses by testing privilege escalation paths
- Cookie manipulation to detect session fixation vulnerabilities
- Timing attack surface analysis on authentication validation functions
- Rate limiting absence on authentication endpoints
The scanner actively probes endpoints with various authentication bypass techniques:
// Example of what middleBrick tests for:
const testAuthenticationBypass = async (url) => {
// Test with missing/invalid tokens
const responses = await Promise.all([
fetch(url, { headers: { authorization: 'Bearer invalid' } }),
fetch(url, { headers: { authorization: '' } }),
fetch(url)
]);
return responses.some(r => r.status === 200); // Should not return 200 without auth
};middleBrick also analyzes OpenAPI specifications for Hapi applications, identifying routes that lack proper authentication annotations:
paths:
/admin/users:
get:
summary: Get all users
# Missing security requirement - vulnerability indicator
# security: []
responses:
'200':
description: User list
The CLI tool provides Hapi-specific scanning capabilities:
npm install -g middlebrick
middlebrick scan https://api.example.com --framework hapi --output jsonGitHub Actions integration allows continuous monitoring of Hapi authentication security:
name: API Security Scan
on: [push, pull_request]
jobs:
security:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Run middleBrick Scan
run: |
npx middlebrick scan ${{ secrets.API_URL }} \
--framework hapi \
--threshold B \
--output sarif > results.sarif
- name: Upload SARIF file
uses: github/codeql-action/upload-sarif@v1
with:
sarif_file: results.sarifHapi-Specific Remediation
Fixing broken authentication in Hapi requires implementing defense-in-depth strategies using Hapi's native authentication ecosystem. Start with proper strategy configuration and consistent application across all routes.
Implement comprehensive authentication strategy with proper validation:
const validateJWT = async (artifacts, request) => {
const decoded = artifacts.decoded;
const user = await findUserById(decoded.sub);
if (!user || !user.active) {
return { isValid: false };
}
// Constant time comparison to prevent timing attacks
const isValid = crypto.timingSafeEqual(
Buffer.from(decoded.iat.toString()),
Buffer.from((Date.now() / 1000).toString())
);
return { isValid: isValid, credentials: user };
};Apply authentication consistently with proper scope validation:
server.route({
method: 'GET',
path: '/admin/users',
options: {
auth: {
strategy: 'jwt',
scope: ['admin']
},
handler: (request, h) => {
// Verify resource ownership
if (!request.auth.credentials.isAdmin) {
return h.response('Forbidden').code(403);
}
return getAllUsers();
}
}
});Implement session regeneration after privilege changes:
const loginHandler = async (request, h) => {
const { username, password } = request.payload;
const user = await authenticateUser(username, password);
// Regenerate session to prevent fixation
request.cookieAuth.revoke();
request.cookieAuth.set(user);
return h.redirect('/dashboard');
};Add rate limiting to authentication endpoints using Hapi's rate limiting plugins:
const ratelimit = require('hapi-rate-limitor');
await server.register({
plugin: ratelimit,
options: {
user: {
strategy: 'user',
expire: 60,
count: 5,
key: request => request.auth.credentials.id
}
}
});
server.route({
method: 'POST',
path: '/login',
options: {
pre: [{ method: 'user' }], // Rate limiting
auth: false,
handler: async (request, h) => {
// Authentication logic
}
}
});Implement proper error handling to prevent information leakage:
const loginHandler = async (request, h) => {
try {
const { username, password } = request.payload;
const user = await findUser(username);
if (!user) {
// Generic error to prevent username enumeration
throw new Error('Invalid credentials');
}
const isValid = await verifyPassword(password, user.passwordHash);
if (!isValid) {
throw new Error('Invalid credentials');
}
return { success: true };
} catch (err) {
return h.response({ error: 'Authentication failed' }).code(401);
}
};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 |