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.