HIGH broken access controlsailsbasic auth

Broken Access Control in Sails with Basic Auth

Broken Access Control in Sails with Basic Auth — how this specific combination creates or exposes the vulnerability

Broken Access Control occurs when an API fails to enforce proper authorization checks between subjects and resources. In Sails, using HTTP Basic Authentication without additional authorization logic can unintentionally expose this weakness. Basic Auth transmits a base64-encoded username:password pair in the request header; while transport confidentiality is typically handled separately, the application must still validate identity and enforce per-action or per-instance permissions.

When Sails controllers rely only on the presence of Basic Auth credentials and skip model-level or policy-level authorization, an authenticated user may operate on records that belong to other users. For example, a request to GET /users/123 may reach a Sails controller that simply loads User.findOne(req.param('id')) after a successful Basic Auth check, without verifying that the authenticated user is allowed to view that specific user record. This is a BOLA/IDOR pattern, a subset of Broken Access Control, and it is especially likely when developers assume Basic Auth alone is sufficient for authorization.

Because Basic Auth is static and often reused across sessions, the risk is compounded if tokens or passwords are weak or leaked. An attacker who intercepts or guesses a credential pair gains a valid identity that the application may treat as sufficient for any action the endpoint permits. In Sails, if policies or controller logic do not enforce ownership, role, or tenant boundaries, the API returns data or performs actions that should be restricted. The 12 parallel security checks in middleBrick include BOLA/IDOR and Authentication, which specifically test whether endpoints properly gate access after verifying identity and enforce context-aware permissions.

Middleware-like behavior in Sails policies can also contribute to the issue. If a policy only attaches req.user from Basic Auth headers but does not integrate with model policies or row-level rules, runtime checks may be inconsistent. For instance, a blueprint route such as /user/:id might bypass custom logic unless explicitly disabled, allowing an authenticated user to iterate IDs and enumerate accessible records. This illustrates why Basic Auth must be paired with explicit authorization checks at the controller or policy layer, rather than treated as a complete access control solution.

middleBrick validates this class of issues by comparing the unauthenticated attack surface behavior with spec-defined security expectations, including required authentication scopes and ownership constraints defined in OpenAPI/Swagger specs with full $ref resolution. This helps identify where endpoints accept authentication but omit granular authorization, providing remediation guidance mapped to frameworks such as OWASP API Top 10 A01:2019 and relevant compliance references.

Basic Auth-Specific Remediation in Sails — concrete code fixes

To secure Sails APIs using Basic Auth, move beyond simple authentication and enforce explicit authorization for each sensitive action. Below are concrete patterns and code examples that demonstrate how to implement proper checks.

1) Require authentication in a policy, then enforce ownership in the controller or model policy. Define a policy that extracts and validates Basic Auth credentials, attaches req.user, and ensures subsequent actions respect ownership.

// config/policies.js
module.exports.policies = {
  UserController: {
    '*': 'authBasic',
    view: 'canViewOwnUser',
    update: 'canEditOwnUser'
  }
};

// api/policies/authBasic.js
module.exports = async function (req, res, next) {
  const authHeader = req.headers.authorization;
  if (!authHeader || !authHeader.startsWith('Basic ')) {
    return res.unauthorized('Missing Basic Auth');
  }
  const base64 = authHeader.split(' ')[1];
  const decoded = Buffer.from(base64, 'base64').toString('utf8');
  const [username, password] = decoded.split(':');
  if (!username || !password) {
    return res.unauthorized('Invalid credentials format');
  }
  const user = await User.findOne({ username, password }); // use hashed passwords in practice
  if (!user) {
    return res.unauthorized('Invalid credentials');
  }
  req.user = user;
  return next();
};

// api/policies/canViewOwnUser.js
module.exports = async function (req, res, next) {
  const targetId = parseInt(req.param('id'), 10);
  if (!req.user || req.user.id !== targetId) {
    return res.forbidden('You cannot view this resource');
  }
  return next();
};

2) Disable blueprint routes for sensitive actions and implement explicit controller methods that always validate ownership. This prevents ID guessing and enumeration even when credentials are valid.

// config/routes.js
module.exports.routes = {
  'GET /user/profile': 'UserController.profile',
  'PUT /user/profile': 'UserController.updateProfile'
  // Remove or set `blueprint` to false for UserController to avoid automatic routes
};

// api/controllers/UserController.js
module.exports = {
  profile: async function (req, res) {
    if (!req.user) {
      return res.unauthorized('Unauthenticated');
    }
    const user = await User.findOne(req.user.id);
    if (!user) {
      return res.notFound();
    }
    return res.ok(user.sanitize());
  },
  updateProfile: async function (req, res) {
    if (!req.user) {
      return res.unauthorized('Unauthenticated');
    }
    const updated = await User.updateOne(req.user.id).set(req.body);
    if (!updated) {
      return res.notFound();
    }
    return res.ok(updated);
  }
};

3) Use role-based checks where applicable, but combine them with instance-level ownership to avoid privilege escalation. Basic Auth may carry roles in headers or a directory service; ensure roles are validated alongside resource ownership.

// api/policies/hasRoleOrOwn.js
module.exports = async function (req, res, next) {
  const requiredRole = req.options.custom && req.options.custom.requiredRole;
  if (!requiredRole) {
    return res.serverError('Policy misconfiguration');
  }
  if (!req.user) {
    return res.unauthorized('Unauthenticated');
  }
  const isOwner = req.user.id === parseInt(req.param('id'), 10);
  const hasRole = req.user.role === requiredRole;
  if (!isOwner && !hasRole) {
    return res.forbidden('Insufficient permissions');
  }
  return next();
};

These patterns ensure that authentication via Basic Auth is treated as an identity assertion rather than an authorization guarantee. By combining policy enforcement, explicit controller logic, and strict ownership checks, you reduce the risk of Broken Access Control in Sails APIs while still leveraging Basic Auth for initial credential validation.

Frequently Asked Questions

Does using Basic Auth in Sails automatically prevent Broken Access Control?
No. Basic Auth only verifies identity at the transport layer. Without explicit ownership or role checks in controllers or policies, an authenticated user can still access or modify other users' resources, resulting in BOLA/IDOR vulnerabilities. Authorization must be implemented separately.
How does middleBrick help detect Broken Access Control in Basic Auth-protected Sails APIs?
middleBrick runs parallel security checks including Authentication and BOLA/IDOR against the unauthenticated attack surface. It compares runtime behavior with definitions in your OpenAPI/Swagger spec (with full $ref resolution) and highlights where authentication is present but granular authorization is missing, providing remediation guidance mapped to frameworks such as OWASP API Top 10.