HIGH broken access controlhapijavascript

Broken Access Control in Hapi (Javascript)

Broken Access Control in Hapi with Javascript — how this specific combination creates or exposes the vulnerability

Broken Access Control occurs when authorization checks are missing, incomplete, or bypassed, allowing authenticated users to access or modify resources they should not. In a Hapi application written in Javascript, this risk is amplified by route-level configuration mistakes, misuse of Hapi’s auth mechanisms, and failure to enforce object-level authorization at the handler layer.

Hapi relies on an auth layer that can be applied at the server, route, or tool level. If a route uses auth: { mode: 'try' } or incorrectly trusts a scope claim without validating ownership, a BOLA (Insecure Direct Object Reference) / IDOR pattern can emerge. For example, a route like /users/{id}/profile that reads a user ID from the URL but does not compare it to the authenticated user’s ID enables horizontal privilege escalation.

Javascript-specific factors increase exposure. Dynamic route parameters, loosely typed request payloads, and callback-based handlers can lead to implicit trust in identifiers. If authorization logic is expressed as ad-hoc conditionals inside handlers rather than centralized policies, it becomes easy to forget checks for edge cases such as nested resources or administrative overrides. Hapi’s plugin system can further fragment policy enforcement if plugins define their own auth schemes without consistent validation.

Consider a vulnerable Hapi route:

const Hapi = require('@hapi/hapi');

const init = async () => {
  const server = Hapi.server({ port: 4000 });

  server.auth.strategy('session', 'cookie', {
    password: 'secret-key-with-min-length',
    cookie: 'app_session',
    validate: async (request, session) => ({
      isValid: !!session.userId,
      credentials: { userId: session.userId }
    })
  });

  server.route({
    method: 'GET',
    path: '/users/{targetId}/settings',
    options: {
      auth: { mode: 'try' },
      handler: async (request, h) => {
        const { targetId } = request.params;
        const { userId } = request.auth.credentials;

        // Missing authorization check: any logged-in user can supply any targetId
        const settings = await db.settings.find({ userId: targetId });
        return settings;
      }
    }
  });

  await server.start();
};

In this example, the route uses mode: 'try', which allows access even when credentials are missing, and the handler uses targetId directly without verifying that it matches the authenticated user. An authenticated user can change the URL to access another user’s settings, demonstrating IDOR. This aligns with findings from middleware like authentication and BOLA/IDOR checks that middleBrick reports when it detects missing or insufficient authorization in API endpoints.

Another common issue arises when Hapi routes accept role claims but do not enforce object-level constraints. For instance, an admin flag in a JWT payload might be used to gate access at the route level, but object ownership is not checked within the handler. Attackers who obtain or forge a token with elevated scopes can exploit this gap to perform privilege escalation, a pattern categorized under BFLA / Privilege Escalation by middleBrick’s parallel security checks.

Additionally, overly permissive CORS and cookie settings can unintentionally expose authenticated sessions to cross-origin abuse, further weakening access control. Because Hapi applications often integrate multiple plugins and route definitions, inconsistent policy implementations across endpoints make it difficult to maintain a uniform authorization boundary, increasing the likelihood of Broken Access Control vulnerabilities.

Javascript-Specific Remediation in Hapi — concrete code fixes

To remediate Broken Access Control in Hapi with Javascript, enforce strict authorization at both the route and handler levels, validate ownership for every object-level operation, and centralize policy checks to avoid ad-hoc conditionals.

Use auth: { mode: 'required' } to ensure credentials are mandatory, and implement scope or role checks that are tied to the specific resource being accessed. Always resolve the target resource owner from the data layer and compare it to the authenticated subject before returning or modifying data.

Here is a secure version of the earlier route:

const Hapi = require('@hapi/hapi');

const init = async () => {
  const server = Hapi.server({ port: 4000 });

  server.auth.strategy('session', 'cookie', {
    password: 'secret-key-with-min-length',
    cookie: 'app_session',
    validate: async (request, session) => ({
      isValid: !!session.userId,
      credentials: { userId: session.userId }
    })
  });

  server.route({
    method: 'GET',
    path: '/users/{targetId}/settings',
    options: {
      auth: { mode: 'required' },
      handler: async (request, h) => {
        const { targetId } = request.params;
        const { userId } = request.auth.credentials;

        // Enforce ownership: ensure the targetId matches the authenticated user
        if (targetId !== userId) {
          throw Boom.unauthorized('You cannot access this resource');
        }

        const settings = await db.settings.find({ userId: targetId });
        return settings;
      }
    }
  });

  await server.start();
};

For role-based scenarios, prefer mapping roles to granular permissions and validating them in the handler or via an authorization plugin rather than relying solely on route-level roles:

server.route({
  method: 'DELETE',
  path: '/records/{recordId}',
  options: {
    auth: { mode: 'required' },
    handler: async (request, h) => {
      const { recordId } = request.params;
      const { userId, role } = request.auth.credentials;

      // Fetch the record to confirm ownership or admin rights
      const record = await db.records.findById(recordId);
      if (!record) {
        throw Boom.notFound();
      }

      if (record.userId !== userId && role !== 'admin') {
        throw Boom.forbidden('Insufficient permissions');
      }

      await db.records.delete(recordId);
      return { deleted: true };
    }
  }
});

Centralize authorization logic where possible, for example by creating a helper that validates subject-to-resource relationships. Avoid trusting URL or payload identifiers without confirming them against the authenticated context. When using plugins, ensure they do not inadvertently relax auth modes or expose internal identifiers without checks.

These steps reduce the risk of IDOR, privilege escalation, and other Broken Access Control patterns, and align with the types of findings that middleBrick surfaces under the Authentication and BOLA/IDOR checks when scanning your API endpoints.

Frequently Asked Questions

Why does using `auth: { mode: 'try' }` in Hapi increase the risk of Broken Access Control?
Using `mode: 'try'` allows requests to proceed even when authentication fails, which can bypass intended authorization checks. This makes it easier for attackers to access resources without proper credentials or ownership validation, increasing the risk of IDOR and privilege escalation.
How can I test for IDOR in my Hapi endpoints during development?
Manually test by authenticating as one user and modifying URL parameters (e.g., {id}) to access another user's resources. Complement this with automated scans using tools like middleBrick, which checks for missing authorization and ownership validation across your API surface.