Auth Bypass in Hapi with Jwt Tokens
Auth Bypass in Hapi with Jwt Tokens — how this specific combination creates or exposes the vulnerability
Hapi is a rich framework for building web applications and services in Node.js, and it is commonly used with JWT-based authentication to protect routes. When JWT validation is configured on Hapi routes, an Auth Bypass can occur if the server-side route definitions do not enforce authentication consistently or if token validation is applied at the wrong layer. For example, a route group or server route may be registered without the authentication strategy, while a subset of routes within the same group rely on JWT protection. This inconsistency means an unauthenticated request can reach a protected handler if the route’s options.auth is omitted or set to false.
Another common pattern that leads to bypass involves how JWTs are validated. If the server uses a permissive validation configuration, such as not validating the issuer (iss), audience (aud), or expiration (exp), an attacker can supply a token that is structurally valid but issued for a different scope or context. In Hapi, the hapi-jwt module or a custom auth strategy can be set to accept any token signed by a trusted key without additional claims checks. This can allow an attacker to reuse a token intended for a less-privileged context if the server does not enforce role or scope claims within the route validation logic.
Route parameter confusion can also contribute to bypass. In Hapi, routes can be nested under path prefixes, and authentication can be applied at the route level rather than the server level. If a developer applies JWT protection to a specific route but forgets to secure an alternative route with a similar path pattern, an unauthenticated request to the unprotected route can access functionality that should be restricted. This is especially risky with dynamic paths where route definitions may be auto-generated or registered conditionally.
Additionally, Hapi’s extensibility with hooks and extensions can introduce subtle gaps. If onPreAuth or onPostAuth hooks modify request state or skip authentication under certain conditions, they can inadvertently allow access to protected resources. For instance, a hook that sets an authenticated flag based on the presence of a token header without verifying the token’s signature or claims can lead to an Auth Bypass. Similarly, misconfigured scope or role checks in JWT validation may pass a token that lacks the required permissions for a given endpoint but still grants access due to incomplete authorization checks beyond token validity.
Finally, the use of introspection or revocation mechanisms can be inconsistent. If JWTs are accepted purely based on signature validity without checking a revocation list or a short-lived token strategy, a stolen or leaked token may remain usable beyond its intended window. In Hapi, without integrating token revocation checks or leveraging reference tokens where feasible, an attacker who obtains a valid JWT can bypass intended access controls until the token naturally expires, assuming the server does not validate additional anti-replay controls.
Jwt Tokens-Specific Remediation in Hapi — concrete code fixes
To secure Hapi routes with JWT, configure authentication at the server or route level with strict validation options. Use the hapi-jwt plugin or a custom strategy to validate tokens, and ensure that every sensitive route requires authentication. The following example shows a secure setup using @hapi/jwt in a Hapi server.
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_KEY,
validate: (decoded, request, h) => {
// Add additional checks: issuer, audience, scope, roles
const isValid = decoded.iss === 'myapp' && decoded.aud === 'api';
if (!isValid) {
return { isValid: false };
}
return { isValid: true, credentials: decoded };
},
verifyOptions: {
algorithms: ['HS256'],
clockTolerance: 2
}
});
server.auth.default('jwt');
server.route([
{
method: 'GET',
path: '/public',
handler: (request, h) => ({ message: 'public data' })
},
{
method: 'GET',
path: '/secure',
options: {
auth: 'jwt',
plugins: {
'hapi-auth-cookie': { redirectTo: false }
},
handler: (request, h) => ({ message: 'secure data', user: request.auth.credentials })
}
}
]);
await server.start();
console.log('Server running on %s', server.info.uri);
};
process.on('unhandledRejection', (err) => {
console.error(err);
process.exit(1);
});
This example sets a server-wide JWT strategy with explicit key configuration and custom validation logic. The verifyOptions restricts the allowed algorithms and adds clock tolerance to handle minor time differences. By setting server.auth.default('jwt'), all routes require authentication unless explicitly overridden. For routes that must remain public, ensure they do not inherit the default auth or specify options.auth as false explicitly.
To prevent scope and role bypass, include checks for custom claims within the validate function. For instance, if an endpoint requires an admin scope, verify decoded.scope includes admin before allowing access. This ensures that tokens issued for read-only contexts cannot be used to invoke administrative actions.
For dynamic route registration, always apply the auth option explicitly when defining routes. Avoid relying on route prefixes or server-level defaults to propagate protection. In the following snippet, a route with a sensitive operation explicitly requires jwt, while a non-sensitive route omits the auth option.
server.route({
method: 'POST',
path: '/admin/users',
options: {
auth: 'jwt',
handler: (request, h) => {
if (!request.auth.credentials.scopes.includes('admin')) {
throw Boom.forbidden('Insufficient scope');
}
// perform admin action
return h.response({ status: 'ok' });
}
}
});
Additionally, prefer using short-lived JWTs and consider integrating token revocation or reference tokens where stateful checks are necessary. While Hapi validates the token signature and claims, application-level checks should complement JWT validation to enforce least privilege and prevent Auth Bypass through misconfigured routes or overly permissive validation rules.
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 |