2.7 KiB
2.7 KiB
Authorization — Derived Rules
Derives from
domains/security/first-principles.mdP2 (Least Privilege), P3 (Defense in Depth), P1 (Zero Trust).
The Default: Deny
- Every request is denied unless explicitly authorized.
- "Authorized by default" is an anti-pattern. The absence of a rule means denial.
- A missing authz check is a bug, not a feature gap.
Authorization Models
RBAC (Role-Based)
- Users have roles; roles have permissions.
- Roles are coarse:
admin,editor,viewer. Permissions are fine:post:create,post:delete. - Check permissions, not roles:
can(user, 'post:create'), notuser.role === 'admin'. - Roles can change; permission checks are stable.
ABAC (Attribute-Based)
- Authorization based on attributes of the user, resource, and context.
- More expressive: "user can edit a post if user.department == post.department and post.status == 'draft'".
- Use when RBAC is too coarse. Beware: complex ABAC is hard to audit.
ReBAC (Relationship-Based)
- Authorization based on relationships (e.g., Zanzibel).
- "user:alice is editor of document:42" — check the relationship graph.
- Scales for fine-grained, resource-specific access (Google Docs-style).
Where to Check (P4 Locality)
- Check at the boundary: the API endpoint, the resolver, the controller.
- Check at the data layer: defense in depth. A query that bypasses the controller still respects row-level security.
- Never check only in the UI. The UI is a convenience, not a security boundary.
Principle of Least Privilege (P2)
- A token/role gets the minimum permissions to do its job.
- No "admin" role for daily work. Admin is for administration; daily work uses a scoped role.
- Service tokens are scoped to one service's resources, not "all resources."
IDOR (Insecure Direct Object Reference) (P1 Zero Trust)
/api/users/123— does the requester own 123? Check.- Never assume the user can access any ID they request. The ID is input; inputs are untrusted.
- Use scoped queries:
User.find({ id, owner: userId }), notUser.find(id).
Caching and Authz (P3 Defense in Depth)
- Authorization is not cached across users. A cached response for user A is not served to user B.
- Cache keys include the user/role, not just the resource.
- "Cache it as public if anyone can see it" — only if truly anyone (no auth).
What Violates Authorization
| Violation | Principle |
|---|---|
/admin endpoint with no authz check |
P1 Zero Trust |
user.role === 'admin' instead of permission check |
P2 (roles change) |
IDOR: User.find(req.params.id) with no ownership check |
P1 Zero Trust |
| Cached authz decision reused across users | P3 Defense in Depth |
| Service token with "all resources" scope | P2 Least Privilege |