HIGH broken access controlexpressapi keys

Broken Access Control in Express with Api Keys

Broken Access Control in Express with Api Keys

Broken Access Control in an Express API that relies on API keys occurs when authorization checks are missing, incomplete, or bypassed, allowing a caller to access resources or endpoints they should not reach. In this context, the API key is the primary credential used for authentication, but if the application does not enforce scope-based or ownership-based authorization on top of key validation, the access control boundary breaks.

Consider an Express route that retrieves user data:

app.get('/users/:id', (req, res) => {
  const apiKey = req.headers['x-api-key'];
  if (!apiKey) return res.status(401).send('Missing API key');
  // Vulnerable: no check that the requesting key is allowed to view this user ID
  const user = getUserById(req.params.id);
  res.json(user);
});

An attacker who knows or guesses another user’s ID can supply a valid API key (possibly their own) and read data belonging to other users. This is a classic BOLA/IDOR pattern enabled by weak authorization. The API key proves identity but does not enforce authorization boundaries, so the access control is effectively broken.

Another common pattern is role or scope misuse:

app.delete('/records/:recordId', (req, res) => {
  const key = req.headers['x-api-key'];
  const record = getRecord(req.params.recordId);
  // Vulnerable: key is valid but caller may not have delete rights on this record
  if (!canDeleteRecord(key, record)) {
    return res.status(403).send('Forbidden');
  }
  deleteRecord(req.params.recordId);
  res.sendStatus(204);
});

If canDeleteRecord is poorly implemented (e.g., only checks that the key exists in a list of active keys without considering record ownership or tenant boundaries), the check provides a false sense of security. This exposes a BFLA/Privilege Escalation vector: a key with broad permissions can act on records it should not touch. Additionally, if the API key is transmitted in logs, query strings without HTTPS, or stored insecurely, data exposure and confidentiality risks increase.

Broken Access Control with API keys also surfaces in mass assignment or property authorization flaws. If an endpoint accepts a JSON body and merges it directly into a database update without filtering fields, a caller with a valid key can modify admin flags or other sensitive attributes:

app.put('/profile/:userId', (req, res) => {
  const key = req.headers['x-api-key'];
  // Vulnerable: attacker could add { isAdmin: true } to the payload
  updateUser(req.params.userId, req.body);
  res.sendStatus(200);
});

Without explicit property-level authorization, a valid API key can lead to privilege escalation across the application. These patterns illustrate how authentication via API keys can coexist with broken access control when authorization checks are missing, inconsistent, or improperly scoped.

Api Keys-Specific Remediation in Express

To fix Broken Access Control when using API keys in Express, move beyond simple key presence checks and enforce explicit authorization per request and per resource. Use a structured approach that ties keys to actors, scopes, and tenant boundaries, and validate every operation against an authorization model.

First, model keys as belonging to a client or tenant, and include metadata such as scopes or allowed resources:

// keyStore.js
const keyMap = new Map([
  ['sk_live_a1b2c3', { ownerId: 'user-123', scopes: ['read:users:own', 'delete:records:own'] }],
  ['sk_live_x9y8z7', { ownerId: 'user-456', scopes: ['read:users:all'] }]
]);
module.exports.getKeyMeta = (key) => keyMap.get(key) || null;

Then enforce ownership and scope checks in routes:

app.get('/users/:id', (req, res) => {
  const apiKey = req.headers['x-api-key'];
  if (!apiKey) return res.status(401).send('Missing API key');
  const keyMeta = getKeyMeta(apiKey);
  if (!keyMeta) return res.status(401).send('Invalid API key');
  // Enforce ownership: only allow reading own data unless broader scope is granted
  if (keyMeta.ownerId !== req.params.id && !keyMeta.scopes.includes('read:users:all')) {
    return res.status(403).send('Forbidden');
  }
  const user = getUserById(req.params.id);
  res.json(user);
});

For deletion or mutation, validate both the scope and the resource ownership explicitly:

app.delete('/records/:recordId', (req, res) => {
  const apiKey = req.headers['x-api-key'];
  const keyMeta = getKeyMeta(apiKey);
  if (!keyMeta) return res.status(401).send('Invalid API key');
  const record = getRecord(req.params.recordId);
  if (!record) return res.status(404).send('Not found');
  // Ensure the key is allowed to delete this specific record
  if (!keyMeta.scopes.includes('delete:records:all') && record.ownerId !== keyMeta.ownerId) {
    return res.status(403).send('Forbidden');
  }
  deleteRecord(req.params.recordId);
  res.sendStatus(204);
});

To guard against mass assignment, use a denylist or allowlist pattern and avoid passing the raw body directly to the update function:

app.put('/profile/:userId', (req, res) => {
  const apiKey = req.headers['x-api-key'];
  const keyMeta = getKeyMeta(apiKey);
  if (!keyMeta) return res.status(401).send('Invalid API key');
  const safeUpdates = {};
  // Explicitly pick allowed fields
  if (typeof req.body.displayName === 'string') safeUpdates.displayName = req.body.displayName;
  if (typeof req.body.email === 'string') safeUpdates.email = req.body.email;
  // Do not merge arbitrary fields
  updateUser(req.params.userId, safeUpdates);
  res.sendStatus(200);
});

These patterns ensure that having a valid API key does not automatically grant unrestricted access. By tying keys to identities and scopes and validating each request against those boundaries, you reduce the risk of BOLA/IDOR and privilege escalation while maintaining a practical key-based authentication model.

Frequently Asked Questions

Why is checking for a valid API key not enough to prevent Broken Access Control?
A valid key proves identity but does not enforce authorization boundaries. Without explicit checks for resource ownership and scope, an attacker can use a valid key to access or modify data they should not reach, resulting in BOLA/IDOR or privilege escalation.
Can API keys support scopes to reduce Broken Access Control risks?
Yes. Model keys with associated metadata such as scopes and owner identifiers, then enforce those scopes in each route. This enables least-privilege access and prevents a key from acting beyond its intended permissions.