Api Rate Abuse in Hapi with Openid Connect
Api Rate Abuse in Hapi with Openid Connect — how this specific combination creates or exposes the vulnerability
Rate abuse in Hapi when OpenID Connect (OIDC) is used for authentication can occur when rate-limiting is applied before token validation or is scoped to unauthenticated identifiers. Without proper alignment between authentication state and rate-limit keys, an attacker can exhaust application resources or bypass intended limits.
Consider a Hapi server that validates OIDC access tokens on each request. If rate-limiting is configured based on IP address or a non-validated claim (for example, an unverified subject identifier), an attacker can generate many tokens for the same user or forge requests that appear to come from different sources. This mismatch allows repeated authentication attempts or token reuse to overwhelm endpoint handlers that are not properly gated.
Another scenario involves endpoints that perform expensive operations after token introspection but before authorization checks. If rate limits are enforced after these steps, an attacker can trigger many token validations that consume CPU and connection capacity. This is particularly risky when token validation includes external calls to the OIDC provider or when introspection endpoints are slow, effectively turning rate abuse into a denial-of-service vector against the OIDC flow itself.
Insecure default configurations in Hapi plugins can also contribute. For example, a rate-limiting policy that applies globally without considering authenticated identities may allow authenticated users to exceed per-user quotas if the identifier used for limiting does not derive from the validated OIDC subject (sub) claim. Attackers who obtain or guess valid tokens can then abuse high-rate endpoints under the identity of a legitimate user while the server incorrectly attributes excess load to other identities.
To detect this pattern during a scan, middleBrick runs checks that correlate authentication state with rate-limit boundaries, testing whether rate limits differ for authenticated versus unauthenticated paths and whether claims like sub are used consistently. The tool also verifies that token-introspection-heavy endpoints are protected by request caps to prevent resource exhaustion through repeated validation attempts.
Proper mitigation ties rate-limit keys to authenticated identities and enforces limits before expensive OIDC validation work. Using normalized, verified claims for limiting ensures that each authenticated subject is subject to the same caps regardless of IP or token issuance quirks. This prevents abuse paths where forged or replayed tokens are used to escalate impact across multiple users or sessions.
Openid Connect-Specific Remediation in Hapi — concrete code fixes
Securely combining OpenID Connect and rate limiting in Hapi requires tying limits to authenticated identities and validating tokens before applying any quota. Below are focused patterns that reduce abuse risk while preserving usability.
1. Validate token first, then enforce rate limits by subject
Ensure that rate limiting operates after successful authentication and uses a stable claim such as sub. This prevents identity spoofing via IP rotation or token guessing.
const Hapi = require('@hapi/hapi');
const { auth } = require('@hapi/boom');
const rateLimit = require('hapi-rate-limit');
const validateOidcToken = async (request, h) => {
const token = request.headers.authorization?.split(' ')[1];
if (!token) return { isValid: false };
// Replace with actual OIDC introspection or JWT verification
const payload = await verifyIdToken(token); // returns { sub, scopes, ... }
if (!payload || !payload.sub) return { isValid: false };
return { isValid: true, credentials: { sub: payload.sub, scopes: payload.scopes } };
};
const server = Hapi.server({ port: 4000 });
server.auth.strategy('oidc', 'custom', {
validate: validateOidcToken
});
// Apply rate limiting after auth, keyed by subject
server.auth.strategy('rateLimitedOidc', 'custom', {
validate: async (request, h) => {
const auth = request.auth;
if (!auth || !auth.credentials || !auth.credentials.sub) {
return { isValid: false };
}
// key by authenticated subject
const isValid = await rateLimit.validate(auth.credentials.sub);
return { isValid, credentials: auth.credentials };
}
});
server.route({
method: 'GET',
path: '/profile',
options: {
auth: 'rateLimitedOidc',
handler: (request, h) => {
return { message: 'Access granted for ' + request.auth.credentials.sub };
}
}
});
async function verifyIdToken(token) {
// stub: implement JWT verification or OAuth introspection
// return { sub: 'user-123', scopes: ['read'] } on success
return { sub: 'user-123', scopes: ['read'] };
}
server.start();
2. Use route-specific limits and reject unauthenticated early
Define strict limits for high-cost endpoints and ensure unauthenticated requests are rejected before consuming OIDC resources.
const rateLimitStrict = require('hapi-rate-limit');
// High-cost endpoint: strict per-subject cap
server.route({
method: 'POST',
path: '/oauth/token',
options: {
auth: 'rateLimitedOidc',
plugins: {
rateLimit: {
limit: 5, // 5 requests per subject per interval
interval: '1m'
}
},
handler: async (request, h) => {
return { token: 'new-access-token' };
}
}
});
// Public endpoint: lighter limit, no auth required but still capped
server.route({
method: 'GET',
path:/.well-known/openid-configuration',
options: {
auth: false,
plugins: {
rateLimit: {
limit: 60,
interval: '1m'
}
},
handler: (request, h) => {
return getOidcConfiguration();
}
}
});
function getOidcConfiguration() {
return {
issuer: 'https://auth.example.com',
authorization_endpoint: 'https://auth.example.com/oauth/authorize',
token_endpoint: 'https://auth.example.com/oauth/token',
userinfo_endpoint: 'https://auth.example.com/userinfo',
jwks_uri: 'https://auth.example.com/certs'
};
}
3. Combine with server-level settings to reduce abuse surface
Configure connection and payload limits to complement route-specific controls, ensuring OIDC validation does not become a bottleneck for legitimate clients.
const server = Hapi.server({
port: 4000,
routes: {
validate: {
failAction: 'log' // do not expose details to clients
}
},
server: {
maxBytes: 1024 * 1024, // 1 MB payload cap
timeout: {
socket: 30000,
client: 60000
}
}
});
4. Monitor and rotate identifiers
Rotate signing keys and introspect token metadata to detect anomalies. Use short-lived tokens and refresh boundaries aligned with rate-limit intervals to reduce replay impact.