Chapter 1 · Practice 3 of 3
Grant the least privilege and fail closed on errors
Why it matters
Least privilege means each user, service and API key gets only what its job needs. OWASP applies it "both horizontally and vertically": horizontally across peers (a user sees their own records, not a colleague's) and vertically across levels (a member cannot reach admin functions). It also recommends periodic reviews to catch privilege creep, where people collect permissions as they change teams and never lose them.
Failing closed is the other half. OWASP's cheat sheet asks you to "ensure all exception and failed access control checks are handled no matter how unlikely they seem". If the permission lookup throws, times out or returns something unexpected, the answer is no:
- Wrap checks so that only an explicit
allowed: truegrants access. - Do not use
catchblocks that log and continue. - Treat a missing session, missing tenant or missing resource the same way: deny, then return the same response.
The OWASP Top 10:2025 made failing open its own category (A10, Mishandling of Exceptional Conditions), which is a good reminder that authorization code is error handling code.
Example
export async function requirePermission(user: User, action: string, resource: Resource) {
let allowed = false;
try {
allowed = (await policy.evaluate({ user, action, resource })) === true;
} catch (err) {
log.error({ err, userId: user.id, action }, 'policy evaluation failed');
}
if (!allowed) throw new ForbiddenError(); // the only way past is an explicit yes
}