Data Exposure in Hapi with Jwt Tokens
Data Exposure in Hapi with Jwt Tokens — how this specific combination creates or exposes the vulnerability
Hapi is a rich framework for building HTTP services in Node.js, and it commonly uses JWT tokens for authentication and authorization. When JWT tokens are handled incorrectly within Hapi, sensitive data can be exposed through multiple vectors. A misconfigured validation strategy, insecure transmission, or improper storage can lead to token leakage or token misuse, resulting in account takeover or privacy violations.
One common pattern in Hapi is to authenticate requests using a JWT payload attached to the request state or using the auth scope provided by the hapi-auth-jwt2 plugin. If the server does not validate the token signature strictly, an attacker could supply a tampered token and gain unauthorized access to protected resources. For example, missing or weak audience (aud) and issuer (iss) checks may allow an attacker to present a token issued for a different service to this API.
Data exposure can also occur when tokens are transmitted over non-HTTPS channels or when debugging endpoints inadvertently log token contents. Hapi servers that log request state or include raw token data in error responses may unintentionally expose tokens in logs or browser consoles. Additionally, storing sensitive claims (such as roles or permissions) in the JWT payload without encryption can lead to information disclosure if the token is intercepted, because JWTs are typically base64-encoded but not encrypted by default.
Another exposure risk arises from improper token scope handling. If a Hapi route that requires limited-scope access receives a token with broader privileges and the server does not enforce scope checks, attackers can perform privilege escalation. The hapi-auth-jwt2 plugin supports scope validation, but developers must explicitly configure it. Failure to validate token expiration (exp) or not refreshing tokens securely can result in the use of stale tokens, increasing the window for data exposure.
Middleware configuration mistakes can compound these issues. For instance, applying authentication strategies globally when only certain routes need protection increases the attack surface. Similarly, failing to set the isSecure flag for cookies when using cookie-based token storage can expose tokens to cross-site scripting (XSS) attacks. These misconfigurations make it easier for an attacker to steal or manipulate JWT tokens and access sensitive data served by the Hapi endpoints.
Jwt Tokens-Specific Remediation in Hapi — concrete code fixes
To mitigate data exposure risks with JWT tokens in Hapi, apply strict validation and secure handling practices. Use the hapi-auth-jwt2 plugin with strong options, validate all standard claims, and avoid logging or exposing token data. The following examples illustrate secure configurations and route handling.
Secure JWT Strategy Configuration
Define a strategy that enforces signature verification, audience, issuer, and scope checks. This prevents acceptance of tampered or misissued tokens.
const Hapi = require('@hapi/hapi');
const Jwt = require('@hapi/jwt');
const validateToken = (decoded, request, h) => {
// Perform additional application-level checks if needed
return { isValid: true, credentials: decoded };
};
const init = async () => {
const server = Hapi.server({
port: 3000,
host: 'localhost'
});
await server.register(Jwt);
server.auth.strategy('jwt', 'jwt', {
keys: process.env.JWT_PUBLIC_KEY || 'your-256-bit-secret',
verify: {
aud: 'my-api.example.com',
iss: 'https://auth.example.com',
scope: 'read write'
},
validate: validateToken
});
server.auth.default('jwt');
server.route({
method: 'GET',
path: '/profile',
options: {
auth: 'jwt',
handler: (request, h) => {
// request.auth.credentials contains the validated decoded payload
return { user: request.auth.credentials.name, scope: request.auth.credentials.scope };
}
}
});
await server.start();
console.log('Server running on %s', server.info.uri);
};
init().catch((err) => {
console.error('Server failed to start:', err);
});
Mitigate Logging and Transmission Risks
Ensure tokens are transmitted only over HTTPS and avoid logging raw tokens. Configure Hapi to use secure cookies when storing tokens on the client side, and sanitize error responses.
const Hapi = require('@hapi/hapi');
const Cookie = require('@hapi/cookie');
const Jwt = require('@hapi/jwt');
const init = async () => {
const server = Hapi.server({
port: 3000,
host: 'localhost',
routes: {
security: {
hsts: {
maxAge: 3600,
includeSubdomains: true
}
}
}
});
await server.register([Jwt, Cookie]);
server.auth.strategy('jwt', 'jwt', {
keys: process.env.JWT_PUBLIC_KEY,
verify: {
aud: 'my-api.example.com',
iss: 'https://auth.example.com',
scope: 'read write'
}
});
server.auth.strategy('cookie', 'cookie', {
cookie: {
name: 'token',
password: process.env.COOKIE_PASSWORD,
isSecure: true,
isHttpOnly: true,
path: '/'
}
});
server.auth.default({
strategy: 'jwt',
mode: 'try'
});
server.route({
method: 'GET',
path: '/secure-data',
options: {
auth: {
strategies: ['jwt'],
scope: ['read']
},
handler: (request, h) => {
if (!request.auth.isAuthenticated) {
return { error: 'Unauthorized' };
}
return { data: 'Sensitive data safely delivered' };
}
}
});
await server.start();
console.log('Secure server running on %s', server.info.uri);
};
init().catch((err) => {
console.error('Server failed to start:', err);
});
Scope and Claim Validation
Always validate scopes and claims on the server side and avoid trusting the client. Enforce scope checks for each route and reject tokens with missing or insufficient scope.
const Hapi = require('@hapi/hapi');
const Jwt = require('@hapi/jwt');
const validateScopes = (decoded, request, h) => {
const requiredScopes = request.route.settings.auth.payload.scope || [];
const tokenScopes = decoded.scope || [];
const hasScope = requiredScopes.every((scope) => tokenScopes.includes(scope));
return { isValid: hasScope, credentials: decoded };
};
const init = async () => {
const server = Hapi.server({
port: 3000,
host: 'localhost'
});
await server.register(Jwt);
server.auth.strategy('jwt', 'jwt', {
keys: process.env.JWT_PUBLIC_KEY,
verify: {
aud: 'my-api.example.com',
iss: 'https://auth.example.com',
scope: ['read', 'write']
},
validate: validateScopes
});
server.route({
method: 'POST',
path: '/admin',
options: {
auth: {
strategies: ['jwt'],
scope: ['write']
},
handler: (request, h) => {
return { message: 'Admin action authorized' };
}
}
});
await server.start();
console.log('Server running with scope validation');
};
init().catch((err) => {
console.error('Server failed to start:', err);
});
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 |