Cors Wildcard in Hapi with Jwt Tokens
Cors Wildcard in Hapi with Jwt Tokens — how this combination creates or exposes the vulnerability
A CORS wildcard (origin: '*') combined with JWT-based authentication in Hapi can unintentionally expose authenticated routes to any domain. When credentials are required (e.g., cookies or authorization headers), browsers block responses to frontend origins that are not explicitly listed. However, a server configured with origin: '*' and credentials: true violates the CORS specification and leads to inconsistent behavior that can be abused in certain contexts.
In a Hapi application, developers sometimes add broad CORS rules to simplify development. For example, using the @hapi/cookie and @hapi/cors plugins with a wildcard origin while also validating JWTs from request headers may appear functional in local testing. In production, this configuration can expose authenticated endpoints to origins that should not be permitted, especially when the JWT is used for authorization and the CORS policy does not restrict origins.
An attacker who can host a malicious webpage may leverage such a misconfiguration if the application relies on cookies for session-like behavior or if the JWT is passed in an exposed header and the CORS rules do not validate the origin. Even without direct credential theft, the presence of a wildcard can aid in cross-origin request forgery scenarios where preflight responses are predictable. MiddleBrick’s checks for CORS misconfiguration as part of its 12 parallel security scans detect overly permissive origins and improper exposure of authenticated routes, including those protected by JWT validation patterns.
Because Hapi applications often integrate multiple plugins for authentication and routing, the risk is not only about the CORS policy itself but also about how the JWT validation layer interacts with origins. If the CORS plugin is configured before authentication in the server setup, requests with invalid or missing JWTs could still receive CORS headers, aiding in reconnaissance. MiddleBrick’s OpenAPI/Swagger analysis correlates runtime CORS responses with the declared spec, helping identify mismatches between documented and actual behavior for JWT-protected endpoints.
Specific attack patterns relevant to this combination include cross-origin preflight flooding and unauthorized origin enumeration. While the JWT remains a strong bearer token, the server-side CORS rules can weaken the effective security boundary. Proper remediation requires aligning the CORS origin list with known frontend domains and ensuring that authentication checks occur before CORS headers are applied.
Jwt Tokens-Specific Remediation in Hapi — concrete code fixes
To secure a Hapi application that uses JWTs, configure CORS with explicit origins and ensure authentication runs before CORS headers are added. Below are concrete, working examples that demonstrate a secure setup using @hapi/hapi, @hapi/cookie, and @hapi/cors.
Secure CORS with explicit origins and JWT validation
Define a whitelist of frontend origins and validate the incoming origin against it. Validate the JWT in an extension point before building the response, and only then allow the request to proceed.
const Hapi = require('@hapi/hapi');
const cookie = require('@hapi/cookie');
const cors = require('@hapi/cors');
const jwt = require('jsonwebtoken');
const frontendOrigins = ['https://app.example.com', 'https://dashboard.example.com'];
const validateJwt = (request, h) => {
const auth = request.headers.authorization;
if (!auth || !auth.startsWith('Bearer ')) {
return { isValid: false };
}
const token = auth.slice(7);
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
return { isValid: true, decoded };
} catch (err) {
return { isValid: false };
}
};
const init = async () => {
const server = Hapi.server({ port: 4000, host: 'localhost' });
await server.register([
cookie,
{
plugin: cors,
options: {
origins: frontendOrigins,
additionalHeaders: ['authorization'],
passCredentials: true
}
}
]);
server.ext('onPreAuth', (request, h) => {
const verification = validateJwt(request, h);
if (!verification.isValid) {
request.auth.credentials = { error: 'invalid_token' };
} else {
request.auth.credentials = verification.decoded;
}
return h.continue;
});
server.route({
method: 'GET',
path: '/api/secure',
options: {
auth: {
mode: 'required',
strategy: 'jwt'
},
handler: (request, h) => {
return { message: 'Access granted', user: request.auth.credentials };
}
}
});
await server.start();
console.log('Server running on %s', server.info.uri);
};
init();
In this example, CORS is limited to known frontend origins, credentials are passed only when the origin is trusted, and JWT validation runs during onPreAuth. This ensures that unauthorized origins do not receive CORS headers and that invalid tokens are rejected before route handlers execute.
Strategy registration with JWT validation
Register an authentication strategy that explicitly checks the JWT and ties it to the request lifecycle. This approach integrates cleanly with Hapi’s auth module and avoids relying on global CORS wildcard settings.
const Hapi = require('@hapi/hapi');
const cookie = require('@hapi/cookie');
const cors = require('@hapi/cors');
const jwt = require('jsonwebtoken');
const frontendOrigins = ['https://app.example.com'];
const validateJwtStrategy = {
name: 'jwt',
authenticate: (request, h) => {
const auth = request.headers.authorization;
if (!auth || !auth.startsWith('Bearer ')) {
return h.authenticated({ credentials: false, error: 'Missing token' });
}
const token = auth.slice(7);
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
return h.authenticated({ credentials: decoded });
} catch (err) {
return h.authenticated({ credentials: false, error: 'Invalid token' });
}
}
};
const init = async () => {
const server = Hapi.server({ port: 4000, host: 'localhost' });
await server.register([
cookie,
{
plugin: cors,
options: {
origins: frontendOrigins,
additionalHeaders: ['authorization'],
passCredentials: true
}
},
{
plugin: {
register: (server, options) => {
server.auth.strategy('jwt', 'custom', validateJwtStrategy);
server.auth.default('jwt');
},
name: 'auth-jwt'
}
}
]);
server.route({
method: 'GET',
path: '/api/profile',
options: {
auth: {
mode: 'required',
strategy: 'jwt'
}
},
handler: (request, h) => {
return { user: request.auth.credentials };
}
});
await server.start();
console.log('Server running on %s', server.info.uri);
};
init();
These examples show how to explicitly define allowed origins, validate JWTs before routing, and avoid relying on wildcard CORS rules. By correlating these runtime behaviors with OpenAPI declarations, MiddleBrick helps verify that documented CORS policies match actual enforcement for JWT-protected endpoints.
Related CWEs: dataExposure
| CWE ID | Name | Severity |
|---|---|---|
| CWE-200 | Exposure of Sensitive Information | HIGH |
| CWE-209 | Error Information Disclosure | MEDIUM |
| CWE-213 | Exposure of Sensitive Information Due to Incompatible Policies | HIGH |
| CWE-215 | Insertion of Sensitive Information Into Debugging Code | MEDIUM |
| CWE-312 | Cleartext Storage of Sensitive Information | HIGH |
| CWE-359 | Exposure of Private Personal Information (PII) | HIGH |
| CWE-522 | Insufficiently Protected Credentials | CRITICAL |
| CWE-532 | Insertion of Sensitive Information into Log File | MEDIUM |
| CWE-538 | Insertion of Sensitive Information into Externally-Accessible File | HIGH |
| CWE-540 | Inclusion of Sensitive Information in Source Code | HIGH |