Api Key Exposure in Restify with Openid Connect
Api Key Exposure in Restify with Openid Connect — how this specific combination creates or exposes the vulnerability
When Restify is configured to use OpenID Connect (OIDC) for authentication, developers sometimes inadvertently expose API keys through the interaction between token validation, route handlers, and error responses. This exposure typically occurs when API keys are passed in HTTP headers or query parameters and are inadvertently logged, reflected in error messages, or accepted from untrusted sources even when OIDC access tokens are also present.
In a Restify service using OIDC, the intended flow is that clients present an OIDC access token (typically a JWT) obtained from an authorization server. The server validates the token and extracts claims to enforce authorization. However, if the application also accepts an API key (for example, via x-api-key header) and processes it before or alongside token validation, the key may be exposed in logs, error pages, or through misconfigured CORS or debug endpoints.
One common pattern that leads to exposure is when route handlers read the API key from the request without verifying that the OIDC token is present and valid first. If the handler returns a detailed error (e.g., 400 Bad Request with the received key echoed in the body), the key can be captured by logs or by an attacker who can trigger error responses. Additionally, if the Restify server mixes authentication schemes without clear separation, an attacker might supply a valid-looking but revoked or stolen API key and observe timing differences or error messages that confirm its presence or format.
Another vector involves introspection or JWKS fetching in OIDC flows. If the Restify server exposes an endpoint that echoes back validated token claims or metadata and also accepts an API key for administrative actions, an unauthenticated attacker might probe the endpoint to learn whether certain API keys are accepted, especially if the server responds differently based on the presence of the key. This can lead to enumeration or confirmation of valid keys without needing to know their values.
Middleware misconfiguration can compound the issue. For example, attaching an API key validation middleware before OIDC verification might allow an attacker to send only the API key and bypass token-based authorization entirely if the middleware inadvertently grants access. Conversely, attaching it after token validation but logging the key for analytics can result in keys being written to logs that are not adequately protected, leading to accidental exposure through log aggregation systems.
These risks are specific to the combination because OIDC introduces multiple credential types (access token, possibly refresh token) and scopes, while API keys are often long-lived secrets treated like passwords. If the Restify application does not strictly separate the validation paths and does not enforce that OIDC tokens are the primary credential, the API key may be accepted, processed, or echoed in ways that violate the principle of least privilege and secure handling of secrets.
Openid Connect-Specific Remediation in Restify — concrete code fixes
To securely integrate OpenID Connect in Restify while preventing API key exposure, ensure that API keys are never used as the primary authentication mechanism when OIDC is in use, and that keys are handled only after robust token validation. Apply strict input validation, avoid echoing secrets in responses or logs, and enforce clear separation of authentication factors.
Secure Restify route with OIDC and optional API key for administrative actions
const restify = require('restify');
const { Issuer } = require('openid-client');
const server = restify.createServer();
server.use(restify.plugins.bodyParser());
let oidcClient;
async function setupOidc() {
const issuer = await Issuer.discover('https://auth.example.com/.well-known/openid-configuration');
oidcClient = new issuer.Client({
client_id: 'my-service',
client_secret: 'super-secret',
});
}
server.pre(async (req, res, next) => {
// Ensure OIDC token is present and valid before processing any API key
const auth = req.headers.authorization;
if (!auth || !auth.startsWith('Bearer ')) {
return next(new restify.UnauthorizedError('Missing Bearer token'));
}
const token = auth.split(' ')[1];
try {
const claims = await oidcClient.introspect(token);
req.claims = claims;
} catch (err) {
return next(new restify.UnauthorizedError('Invalid token'));
}
next();
});
// Admin endpoint that accepts an API key only after OIDC validation
server.post('/admin/keys', (req, res, next) => {
// API key must be provided in a server-controlled header by an internal caller,
// never echoed back in error responses.
const apiKey = req.headers['x-internal-api-key'];
if (!apiKey || apiKey !== process.env.INTERNAL_API_KEY) {
return next(new restify.ForbiddenError('Invalid internal key'));
}
// Proceed with admin action
res.send(200, { ok: true });
return next();
});
server.listen(8080, () => {
console.log('Server listening on port 8080');
});