Broken Access Control in Hapi with Api Keys
Broken Access Control in Hapi with Api Keys — how this specific combination creates or exposes the vulnerability
Broken Access Control occurs when API endpoints do not properly enforce authorization, allowing one user to access or modify resources belonging to another user. In Hapi, this risk is elevated when relying solely on API keys without implementing per-request ownership checks or scoping. An API key is often treated as a global credential that grants broad access, which can map to a high-level role or tenant without additional context. If routes do not validate that the requesting key is authorized for the specific resource identifier in the URL, an attacker can manipulate IDs (e.g., /users/123 → /users/124) and perform BOLA/IDOR attacks.
Hapi does not enforce authorization by default; route handlers must explicitly check permissions. When developers use API keys for authentication but omit authorization logic tied to the key’s scope, they expose endpoints that should be user- or tenant-specific. For example, an endpoint like GET /api/invoices/{id} that only checks for a valid API key in the headers can be exploited if the key is shared across multiple users within an organization. The key may authenticate the request, but it does not guarantee the caller is allowed to view that particular invoice. This gap between authentication (key present) and authorization (key permitted for this resource) is the core of Broken Access Control in this context.
Another common pattern is using API keys in query parameters or headers without rate limiting or audit controls, which can enable privilege escalation. If a key with elevated permissions is accidentally embedded in client-side code or logged in plaintext, an attacker who discovers the key can perform actions beyond their intended scope. The 12 parallel security checks in middleBrick include BOLA/IDOR and Property Authorization, which specifically test whether endpoints validate resource ownership and field-level access. These checks correlate API key usage with route-level permissions and can surface cases where keys bypass proper authorization, such as missing ownership filters in database queries or unrestricted admin-level routes.
Real-world attack patterns mirror findings from OWASP API Top 10 A01: Broken Access Control. For instance, an unauthenticated or low-privilege attacker may probe endpoints with modified identifiers to test if a valid API key grants access to other users’ data. middleBrick’s BOLA/IDOR checks attempt such scenarios by varying resource IDs while keeping the same key. If the API responds with 200 and sensitive data, the scan reports a high-severity finding. This demonstrates that the combination of Hapi routes, API key usage, and missing per-request authorization creates a tangible risk that can lead to data leakage across tenants or users.
To illustrate, consider a Hapi server with an endpoint that retrieves user profiles. If the route only verifies the presence of an API key but does not ensure the key maps to the requested user ID, an attacker can iterate through numeric IDs and harvest other users’ profiles. middleBrick’s Inventory Management and Data Exposure checks help detect whether responses contain excessive data or lack proper filtering. By correlating spec definitions (OpenAPI 2.0/3.0/3.1 with full $ref resolution) with runtime behavior, the scanner identifies mismatches where documentation implies strict scoping but the implementation does not enforce it.
Api Keys-Specific Remediation in Hapi — concrete code fixes
Remediation focuses on ensuring that API key authentication is coupled with explicit authorization checks per request. In Hapi, you should validate not only that a key is valid but also that it is permitted for the specific resource being accessed. Use route prevalidation or handler logic to enforce ownership or scope. Below are concrete code examples that demonstrate secure patterns.
Example 1: Scoped API key with resource ownership check
const Hapi = require('@hapi/hapi');
// Mock key-to-user mapping and resource access rules
const keyScopes = {
'key-user-a': { userId: 'user-a', roles: ['user'] },
'key-admin-1': { userId: null, roles: ['admin'] },
};
const validateKey = async (request, h) => {
const key = request.headers['x-api-key'];
if (!key || !keyScopes[key]) {
return { isValid: false };
}
const scope = keyScopes[key];
return { isValid: true, credentials: scope };
};
const server = Hapi.server({ port: 4000 });
server.route({
method: 'GET',
path: '/users/{{userId}}',
options: {
pre: [
{
assign: 'auth',
method: validateKey,
},
],
handler: (request, h) => {
// Enforce that user can only access their own resource unless admin
const { credentials } = request.pre.auth;
const { userId } = request.params;
if (credentials.roles.includes('admin')) {
return { data: `Admin data for user ${userId}` };
}
if (credentials.userId !== userId) {
return h.response({ error: 'Forbidden' }).code(403);
}
return { data: `User data for ${userId}` };
},
},
});
server.start();
Example 2: Key-to-tenant mapping with explicit IDOR guard
const Hapi = require('@hapi/hapi');
// Simulated database
const invoices = {
'tenant-1': [{ id: '1', amount: 100 }, { id: '2', amount: 200 }],
'tenant-2': [{ id: '3', amount: 300 }],
};
const tenantForKey = {
'key-tenant-a': 'tenant-1',
'key-tenant-b': 'tenant-2',
};
const validateTenantKey = async (request, h) => {
const key = request.headers['x-api-key'];
const tenant = tenantForKey[key];
if (!tenant) {
return { isValid: false };
}
return { isValid: true, credentials: { tenant } };
};
const server = Hapi.server({ port: 5000 });
server.route({
method: 'GET',
path: '/invoices/{invoiceId}',
options: {
pre: [
{
assign: 'tenantCheck',
method: validateTenantKey,
},
],
handler: (request, h) => {
const { tenant } = request.pre.tenantCheck.credentials;
const { invoiceId } = request.params;
const tenantInvoices = invoices[tenant] || [];
const invoice = tenantInvoices.find((inv) => inv.id === invoiceId);
if (!invoice) {
return h.response({ error: 'Forbidden or not found' }).code(403);
}
return invoice;
},
},
});
server.start();
Additional hardening tips
- Always treat API keys as opaque tokens; avoid embedding user or tenant identifiers in the key itself unless you can map and verify scope server-side.
- Combine API key authentication with route-level authorization that checks resource ownership (e.g., ensure invoice ID maps to the tenant derived from the key).
- Use middleware to enforce that keys cannot access admin-only routes unless the key’s scope explicitly allows it.
- Log and monitor key usage to detect anomalies, such as a key suddenly accessing many different resource IDs, which may indicate probing.
- In CI/CD, integrate middleBrick’s GitHub Action to fail builds if scans detect missing authorization checks on key-protected endpoints.