Broken Access Control in Hapi with Jwt Tokens
Broken Access Control in Hapi with Jwt Tokens
Broken Access Control in a Hapi application that uses JWT tokens typically occurs when authorization checks are incomplete, inconsistent, or bypassed, allowing an authenticated user to access or modify resources they should not. Even when JWTs are used for authentication, access control flaws persist if the server relies solely on token presence rather than validating scopes, roles, or resource ownership. In Hapi, this can happen when route validation is limited to verifying a JWT signature while omitting fine-grained permission checks on the handler side.
Consider a Hapi route that reads user data without verifying that the requesting user owns the requested resource ID. If the JWT contains a user identifier but the server does not enforce that identifier against the resource in the handler, a BOLA/IDOR (Broken Level of Authorization/Insecure Direct Object Reference) pattern emerges. For example, an authenticated user could increment an ID parameter to access another user’s profile simply because the route does not compare the subject claim in the token with the requested resource ID.
Another common pattern in Hapi is over-permissive scopes. A JWT may include a scope claim such as read:posts, but the server may fail to enforce scope-based checks on write routes. If a route for updating or deleting a post only checks for a valid JWT and does not require a scope like write:posts, an attacker with a read-only token can perform unauthorized mutations. This is a BFLA/Privilege Escalation issue where the access control logic does not align with the token’s intended rights.
JWT tokens may also carry roles or groups, and Hapi applications can become vulnerable if role checks are inconsistently applied across similar endpoints. For instance, an admin-only endpoint might lack explicit role verification because the developer assumes the token is issued only for admins. If token issuance logic is elsewhere and tokens are later reused across environments, a user with a non-admin token could reach admin routes. This inconsistency is a classic Broken Access Control failure in the context of Hapi with JWT tokens.
Middleware that attaches user information from the token to the request object can further obscure flaws. If authorization decorators or route helpers assume the presence of certain claims without validating them on each request, missing checks can lead to insecure direct object references. For example, attaching a user ID from the token to request.auth.credentials is helpful, but routes must still compare that ID with the target resource ID on every invocation. Without these per-route checks, the attack surface remains wide despite the use of signed JWTs.
Remediation requires aligning token validation with explicit access control logic on every route that accesses sensitive data or operations. In Hapi, this means combining JWT verification with resource-level ownership checks and scope/role enforcement. Developers should treat JWTs as proof of identity, not as a complete authorization mechanism, and implement layered checks that confirm the token’s claims against the specific resource and action being requested.
Jwt Tokens-Specific Remediation in Hapi
To remediate Broken Access Control in Hapi when using JWT tokens, enforce explicit authorization checks in route handlers and leverage Hapi’s built-in validation and extensibility features. Always verify the token, extract claims, and then compare those claims with the requested resource and required permissions. Below are concrete code examples that demonstrate how to implement these controls correctly.
First, ensure JWT validation is performed via a strategy, and then add per-route authorization logic. For example, a user profile route should compare the subject (sub) claim in the token with the user ID in the URL:
// server.js
const Hapi = require('@hapi/hapi');
const Jwt = require('@hapi/jwt');
const init = async () => {
const server = Hapi.server({ port: 3000, host: 'localhost' });
await server.register(Jwt);
server.auth.strategy('jwt', 'jwt', {
keys: process.env.JWT_SECRET,
validate: (decoded, request, h) => {
// You can add additional checks here, e.g., token revocation lists
return { isValid: true, credentials: decoded };
}
});
server.route({
method: 'GET',
path: '/users/{id}',
options: {
auth: 'jwt',
handler: (request, h) => {
const userIdFromToken = request.auth.credentials.sub;
const userIdFromParams = request.params.id;
if (userIdFromToken !== userIdFromParams) {
throw Boom.forbidden('You can only access your own profile');
}
// Fetch and return user data safely
return { id: userIdFromParams, name: 'Alice' };
}
}
});
await server.start();
};
init().catch(err => console.error(err));
For endpoints that require specific scopes, validate the scope claim in the JWT before performing the operation:
// routes/posts.js
const Boom = require('@hapi/boom');
exports.updatePost = {
auth: 'jwt',
handler: (request, h) => {
const { scope } = request.auth.credentials;
const scopes = scope ? scope.split(' ') : [];
if (!scopes.includes('write:posts')) {
throw Boom.forbidden('Insufficient scope for this action');
}
const postId = request.params.id;
const userId = request.auth.credentials.sub;
// Additional check: ensure the user owns the post or has elevated rights
if (!userCanEditPost({ userId, postId })) {
throw Boom.forbidden('You cannot edit this post');
}
// Proceed with update
return { success: true };
}
};
For admin-only routes, explicitly check roles or groups carried in the token:
// routes/admin.js
exports.deleteUser = {
auth: 'jwt',
handler: (request, h) => {
const { roles } = request.auth.credentials;
if (!roles || !roles.includes('admin')) {
throw Boom.forbidden('Admin access required');
}
const targetUserId = request.params.userId;
// Perform deletion with ownership or audit checks as needed
return { deleted: targetUserId };
}
};
Use route-level pre-auth handlers to centralize common checks and reduce repetition. This keeps each handler focused while ensuring access control is consistently applied across endpoints that use JWT tokens in Hapi.