61 lines
2.7 KiB
Markdown
61 lines
2.7 KiB
Markdown
# Authorization — Derived Rules
|
|
|
|
> Derives from `domains/security/first-principles.md` P2 (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')`, not `user.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 })`, not `User.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 | |