HIGH bola idorhapijwt tokens

Bola Idor in Hapi with Jwt Tokens

Bola Idor in Hapi with Jwt Tokens — how this specific combination creates or exposes the vulnerability

Broken Object Level Authorization (BOLA) occurs when an API exposes endpoints that accept user-supplied identifiers and return or modify resources without verifying that the requesting identity has permission for that specific object. In Hapi, this commonly appears in routes that use JWT tokens for authentication but then skip authorization checks on the resource identifier. A JWT may prove identity (e.g., a user ID in a payload), but if the server uses that identity only for authentication and then directly uses an incoming parameter such as id or ownerId to locate a record, an attacker can change the parameter to access or act on another user’s object.

Consider a Hapi route that retrieves a user profile by ID. The route authenticates the caller via JWT and extracts a subject claim, but then fetches the profile using a path parameter controlled by the client without confirming that the profile belongs to the authenticated subject:

// Insecure example: BOLA risk with JWT in Hapi
const Hapi = require('@hapi/hapi');
const jwt = require('@hapi/jwt');

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

  server.register(jwt);

  server.auth.strategy('jwt', 'jwt', {
    keys: process.env.JWT_SECRET,
    validate: (artifacts, request, h) => {
      // artifacts.decoded typically contains { sub: 'user-123', ... }
      return { isValid: true, credentials: { userId: artifacts.decoded.sub } };
    }
  });

  server.route({
    method: 'GET',
    path: '/profiles/{id}',
    config: {
      auth: 'jwt',
      handler: async (request, h) => {
        const { id } = request.params;       // attacker-controlled
        const { userId } = request.auth.credentials; // from JWT

        // Missing: verify that id === userId
        const profile = await db.profiles.findOne({ where: { id } });
        if (!profile) {
          return h.response({ error: 'Not found' }).code(404);
        }
        return profile;
      }
    }
  });

  await server.start();
};
init();

An authenticated user with a valid JWT containing sub: "user-123" can change /profiles/123 to /profiles/124 and access another user’s profile. Because the authorization check is absent, the server treats the object identifier as trustworthy input rather than an access-controlled reference. This pattern extends to mutations (PUT/PATCH/DELETE) and to associations where parent identifiers are exposed (e.g., /users/{userId}/posts/{postId}) without verifying that the authenticated user owns the parent resource. The presence of JWT tokens does not prevent BOLA; it only identifies who is making the request, not whether that user is allowed to act on the supplied object.

BOLA in Hapi is often compounded by implicit trust in path parameters, query strings, or body fields used as keys. Even with JWT-based authentication, developers must enforce that the authenticated subject matches the resource being accessed. Frameworks like Hapi provide request pre-processing and validation extensions, but if those are not used to assert ownership, the attack surface remains wide. Attack patterns include horizontal privilege escalation (user to user) and vertical escalation when object ownership is misaligned with role or tenant boundaries.

Jwt Tokens-Specific Remediation in Hapi — concrete code fixes

Remediation focuses on ensuring that every data access or mutation validates the relationship between the authenticated subject from the JWT and the object identifier in the request. The JWT should establish identity, and the handler must enforce that the requested resource belongs to that identity (or is explicitly shared with them).

First, maintain the JWT validation strategy but explicitly require the subject claim and use it in authorization logic:

// Secure example: BOLA mitigation in Hapi with JWT
const Hapi = require('@hapi/hapi');
const jwt = require('@hapi/jwt');

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

  server.register(jwt);

  server.auth.strategy('jwt', 'jwt', {
    keys: process.env.JWT_SECRET,
    validate: (artifacts, request, h) => {
      if (!artifacts.decoded.sub) {
        return { isValid: false };
      }
      return { isValid: true, credentials: { userId: artifacts.decoded.sub } };
    }
  });

  server.route({
    method: 'GET',
    path: '/profiles/{id}',
    config: {
      auth: 'jwt',
      handler: async (request, h) => {
        const { id } = request.params;
        const { userId } = request.auth.credentials;

        // Enforce BOLA: ensure the requested profile belongs to the authenticated user
        if (id !== userId) {
          return h.response({ error: 'Forbidden' }).code(403);
        }

        const profile = await db.profiles.findOne({ where: { id, userId } });
        if (!profile) {
          return h.response({ error: 'Not found' }).code(404);
        }
        return profile;
      }
    }
  });

  await server.start();
};
init();

For nested resources, propagate ownership checks through the path hierarchy. For example, with posts owned by users, verify both user and post ownership:

// Secure nested resource: BOLA check for user posts
server.route({
  method: 'GET',
  path: '/users/{userId}/posts/{postId}',
  config: {
    auth: 'jwt',
    handler: async (request, h) => {
      const { userId, postId } = request.params;
      const { userId: subjectId } = request.auth.credentials;

      if (userId !== subjectId) {
        return h.response({ error: 'Forbidden' }).code(403);
      }

      const post = await db.posts.findOne({ where: { id: postId, userId } });
      if (!post) {
        return h.response({ error: 'Not found' }).code(404);
      }
      return post;
    }
  }
});

Use centralized authorization helpers or policies to avoid repeating checks and to ensure consistency. Validate that identifiers are of the expected type and format to prevent type confusion or injection-based bypasses. Even when using JWT tokens, always treat incoming object identifiers as untrusted and confirm that the authenticated subject has the right to access or modify them.

FAQ

  • Does using JWT tokens prevent BOLA in Hapi?

    No. JWT tokens authenticate requests and identify users, but they do not enforce object-level permissions. Developers must still verify that the authenticated subject is allowed to access the specific resource identified by path or query parameters.

  • What are some common signs of BOLA in Hapi routes using JWT?

    Common signs include routes that accept an object identifier (e.g., /items/{id}) and only check for a valid JWT without comparing the identifier to the authenticated user’s identity, or routes that use user-supplied foreign keys (e.g., ownerId) without ensuring alignment with the JWT subject.

Related CWEs: bolaAuthorization

CWE IDNameSeverity
CWE-250Execution with Unnecessary Privileges HIGH
CWE-639Insecure Direct Object Reference CRITICAL
CWE-732Incorrect Permission Assignment HIGH

Frequently Asked Questions

Does using Jwt tokens prevent Bola in Hapi?
No. JWT tokens authenticate requests and identify users, but they do not enforce object-level permissions. Developers must still verify that the authenticated subject is allowed to access the specific resource identified by path or query parameters.
What are some common signs of Bola in Hapi routes using Jwt?
Common signs include routes that accept an object identifier (e.g., '/items/{id}') and only check for a valid JWT without comparing the identifier to the authenticated user's identity, or routes that use user-supplied foreign keys (e.g., 'ownerId') without ensuring alignment with the JWT subject.