Gateway routes that proxy upstream APIs now use a dedicated API key authentication middleware.
The middleware accepts either of these request formats:
Authorization: Bearer <api_key>X-Api-Key: <api_key>X-Api-Key is read first. If it is absent, the middleware parses Authorization: Bearer <api_key>. A malformed Authorization header returns 401 only when X-Api-Key is not present.
For each gateway request, the middleware:
- Extracts the presented API key from
X-Api-Key, or fromAuthorization: Bearer <api_key>whenX-Api-Keyis absent. - Derives the key prefix from the first 16 characters.
- Looks up candidate key records by prefix.
- Verifies the full key using a timing-safe hash comparison.
- Rejects revoked keys with
403 Forbidden. - Resolves and attaches:
req.userreq.vaultreq.apireq.endpointreq.apiKeyRecordreq.apiKeyValue
Rate limiting and balance checks remain separate middleware or route concerns and run after authentication.
The middleware supports optional scope enforcement. When a requiredScope is
configured on the middleware factory, the middleware checks that the presented
API key includes that scope before allowing the request through.
Scope resolution rules:
- If the key record has no
scopesarray or it is empty, it defaults to['read'](backward compatibility for legacy keys). - If the key's scopes include
'*', all scopes are allowed (wildcard). - Otherwise, the required scope must appear literally in the key's scopes array.
Route configuration example:
// Only allow keys with the 'write' scope
createGatewayApiKeyAuthMiddleware({
requiredScope: 'write',
// ... other options
});Scopes at key creation:
The POST /apis/:apiId/keys endpoint accepts a scopes field in the request
body (defaults to ['*']). The setter can restrict this to any combination of
read, write, gateway, or any custom string.
Migration 0007_api_key_scopes.sql ensures the scopes TEXT[] column exists
on api_keys and backfills existing keys with '{read}'.
The middleware returns clear 401 responses for common auth failures:
Unauthorized: missing API keyUnauthorized: malformed Authorization headerUnauthorized: API key not foundUnauthorized: invalid API keyUnauthorized: API key does not grant access to this API
If the API key has been revoked, it returns 403 Forbidden with this message:
Unauthorized: API key has been revoked
If the API key lacks the required scope, it returns 403 Forbidden:
Forbidden: API key lacks required scope
If the target API cannot be resolved, it returns:
404 Not Found: unknown API
The middleware is applied to the upstream proxy routes in:
src/routes/gatewayRoutes.tssrc/routes/proxyRoutes.ts
The route handlers then consume the attached request context instead of re-validating headers inline.
The database-backed middleware supports:
- prefix lookup from
api_keys.prefix - full-key hash verification against
api_keys.key_hash - revoked-key enforcement from
api_keys.revoked - eager loading of related
usersandvaults
To support revocation in environments that do not yet have the column, apply:
migrations/0005_add_api_key_revocation.sql
Migration 0006_api_key_prefix_unique.sql adds a partial unique index on
api_keys (prefix) WHERE revoked = FALSE. This guarantees that the
prefix-based lookup in step 3 of the validation flow always returns at most one
active candidate, eliminating any ambiguity before the full hash comparison.
Revoked keys are excluded from the index so a prefix can be legitimately reused after a key is revoked (e.g. after rotation).
Constraint regression tests live in:
src/repositories/apiKeyRepository.prefix.test.ts