LEARN AUTHORIZATION DEEP DIVE
Learn Authorization: From Zero to Access Control Master
Goal: Deeply understand authorization systems—from basic permission checks to building sophisticated policy engines, RBAC, ABAC, and relationship-based access control like Google’s Zanzibar.
Why Authorization Matters
Every application with more than one user needs authorization. While authentication answers “Who are you?”, authorization answers “What are you allowed to do?”. It’s the invisible guardian that decides:
- Can this user delete that file?
- Can this employee approve their own expense report?
- Can this API client access this customer’s data?
- Can this doctor view this patient’s records?
Get it wrong, and you have a security breach. Get it right, and users don’t even notice it’s there.
After completing these projects, you will:
- Understand the fundamental models of access control (DAC, MAC, RBAC, ABAC, ReBAC)
- Build permission systems from scratch
- Implement policy engines that evaluate complex rules
- Design multi-tenant authorization for SaaS applications
- Understand how companies like Google, Airbnb, and Slack handle authorization at scale
Core Concept Analysis
Authorization vs Authentication
┌─────────────────────────────────────────────────────────────────┐
│ Security Flow │
├─────────────────────────────────────────────────────────────────┤
│ │
│ User → [Authentication] → Identity → [Authorization] → Access │
│ "Who are you?" "What can you do?" │
│ │
└─────────────────────────────────────────────────────────────────┘
The Three Questions of Authorization
Every authorization decision answers these questions:
- Subject: Who is requesting access? (user, service, API key)
- Action: What do they want to do? (read, write, delete, approve)
- Resource: What do they want to do it to? (file, record, API endpoint)
Access Control Models
┌────────────────────────────────────────────────────────────────────────────┐
│ Access Control Evolution │
├────────────────────────────────────────────────────────────────────────────┤
│ │
│ ACL (1970s) → RBAC (1990s) → ABAC (2000s) → ReBAC │
│ "List who can "Assign roles" "Use attributes" "Model │
│ access what" relations" │
│ │
│ Simple but Scalable but Flexible but Modern, │
│ doesn't scale rigid complex graph-based │
│ │
└────────────────────────────────────────────────────────────────────────────┘
Fundamental Concepts
- Discretionary Access Control (DAC): Resource owners decide who can access
- Example: Unix file permissions, Google Drive sharing
- Mandatory Access Control (MAC): System-enforced policies, users can’t override
- Example: Military classification levels (TOP SECRET > SECRET > CONFIDENTIAL)
- Role-Based Access Control (RBAC): Permissions assigned to roles, roles assigned to users
- Example: Admin, Editor, Viewer roles in a CMS
- Attribute-Based Access Control (ABAC): Decisions based on attributes of subject, resource, action, and environment
- Example: “Managers can approve expenses under $1000 during business hours”
- Relationship-Based Access Control (ReBAC): Permissions derived from relationships in a graph
- Example: “Users can view files in folders they have access to” (Google Zanzibar)
Key Terminology
| Term | Definition |
|---|---|
| Principal | Entity requesting access (user, service account, API key) |
| Resource | Thing being accessed (file, record, API endpoint) |
| Action/Permission | Operation being performed (read, write, delete) |
| Policy | Rule that determines if access is allowed |
| Role | Named collection of permissions |
| Scope | Context limiting where permissions apply |
| Claim | Assertion about a principal (from a JWT or session) |
| Grant | Explicit permission assignment |
| Deny | Explicit permission refusal (usually takes precedence) |
Project Progression Map
Level 1 (Beginner) Level 2 (Intermediate) Level 3 (Advanced)
───────────────────── ──────────────────────── ─────────────────────
┌─────────────────┐ ┌─────────────────────┐ ┌──────────────────┐
│ 1. Permission │ │ 5. Claims-Based │ │ 11. Policy │
│ Checker │───────▶ │ Authorization │──────▶ │ Engine (OPA) │
└─────────────────┘ └─────────────────────┘ └──────────────────┘
│ │ │
▼ ▼ ▼
┌─────────────────┐ ┌─────────────────────┐ ┌──────────────────┐
│ 2. Basic RBAC │ │ 6. Hierarchical │ │ 12. Zanzibar │
│ System │───────▶ │ Permissions │──────▶ │ Clone │
└─────────────────┘ └─────────────────────┘ └──────────────────┘
│ │ │
▼ ▼ ▼
┌─────────────────┐ ┌─────────────────────┐ ┌──────────────────┐
│ 3. ACL │ │ 7. Multi-Tenant │ │ 13. Distributed │
│ Implementation│───────▶ │ Authorization │──────▶ │ AuthZ │
└─────────────────┘ └─────────────────────┘ └──────────────────┘
│ │ │
▼ ▼ ▼
┌─────────────────┐ ┌─────────────────────┐ ┌──────────────────┐
│ 4. Unix-Style │ │ 8. ABAC System │ │ 14. AuthZ │
│ Permissions │───────▶ │ │──────▶ │ Service Mesh │
└─────────────────┘ └─────────────────────┘ └──────────────────┘
│
▼
┌─────────────────────┐
│ 9. Row-Level │
│ Security │
└─────────────────────┘
│
▼
┌─────────────────────┐
│ 10. OAuth 2.0 │
│ Scopes │
└─────────────────────┘
Project 1: Simple Permission Checker
- File: LEARN_AUTHORIZATION_DEEP_DIVE.md
- Main Programming Language: Python
- Alternative Programming Languages: Go, TypeScript, Rust
- Coolness Level: Level 2: Practical but Forgettable
- Business Potential: 1. The “Resume Gold”
- Difficulty: Level 1: Beginner
- Knowledge Area: Access Control / Boolean Logic
- Software or Tool: Permission Checker Library
- Main Book: “Designing Data-Intensive Applications” by Martin Kleppmann
What you’ll build: A permission checking library that evaluates whether a subject can perform an action on a resource, with support for explicit grants, denies, and wildcards.
Why it teaches authorization: This is the atomic unit of authorization. Every complex system eventually calls a function like can(user, action, resource). Understanding this foundation is essential before adding complexity.
Core challenges you’ll face:
- Modeling the permission check → maps to understanding the subject-action-resource triple
- Handling wildcards and patterns → maps to permission inheritance and grouping
- Deny overrides allow → maps to conflict resolution in access control
- Efficient lookup → maps to data structure design for permissions
Key Concepts:
- Access Control Fundamentals: “Computer Security” Chapter 4 - Matt Bishop
- Boolean Logic in Security: “Security in Computing” Chapter 2 - Pfleeger
- Set Theory for Permissions: “Discrete Mathematics” Chapter 2 - Rosen
Difficulty: Beginner Time estimate: Weekend Prerequisites: Basic programming, understanding of sets and dictionaries
Real world outcome:
# Your library in action
from permcheck import PermissionChecker
checker = PermissionChecker()
# Grant permissions
checker.grant("alice", "read", "document:123")
checker.grant("alice", "write", "document:*")
checker.deny("alice", "delete", "document:*")
# Check permissions
checker.can("alice", "read", "document:123") # True
checker.can("alice", "write", "document:456") # True (wildcard)
checker.can("alice", "delete", "document:123") # False (explicit deny)
checker.can("bob", "read", "document:123") # False (no grant)
# Explain decisions
checker.explain("alice", "delete", "document:123")
# Output: DENIED - Explicit deny rule: deny(alice, delete, document:*)
Implementation Hints:
The core data structure is a set of tuples representing grants and denies:
Grants: {(subject, action, resource), ...}
Denies: {(subject, action, resource), ...}
The decision algorithm:
- Check for explicit deny (with wildcard expansion) → if found, return False
- Check for explicit grant (with wildcard expansion) → if found, return True
- Default deny (closed policy) or default allow (open policy)
Wildcard matching:
*matches any single segment**matches any number of segments- Example:
document:*matchesdocument:123but notdocument:folder:123
Consider these questions:
- What happens if both
grant(alice, read, *)anddeny(alice, read, secret)exist? - How do you handle the order of evaluation?
- What’s the most efficient data structure for wildcard matching?
Learning milestones:
- Basic grant/deny works → You understand the permission triple
- Wildcards work correctly → You understand pattern matching in permissions
- Deny always wins → You understand conflict resolution
- Explain function works → You can trace authorization decisions
Project 2: Basic RBAC System
- File: LEARN_AUTHORIZATION_DEEP_DIVE.md
- Main Programming Language: Python
- Alternative Programming Languages: Go, Java, TypeScript
- Coolness Level: Level 3: Genuinely Clever
- Business Potential: 2. The “Micro-SaaS / Pro Tool”
- Difficulty: Level 1: Beginner
- Knowledge Area: Role-Based Access Control
- Software or Tool: RBAC Engine
- Main Book: “Designing Data-Intensive Applications” by Martin Kleppmann
What you’ll build: A complete RBAC system with users, roles, and permissions. Users are assigned roles, roles have permissions, and you check if a user can perform an action by traversing this hierarchy.
Why it teaches authorization: RBAC is the most common authorization model in enterprise software. Understanding how roles abstract permissions and how users inherit permissions through roles is fundamental to real-world authorization.
Core challenges you’ll face:
- Role-permission mapping → maps to understanding indirection in access control
- User-role assignment → maps to identity-to-privilege binding
- Permission inheritance through roles → maps to transitive permission resolution
- Role hierarchies → maps to inheritance and organizational structures
Key Concepts:
- RBAC96 Model: “Role-Based Access Control” Chapter 3 - Ferraiolo, Kuhn, Chandramouli
- Permission Composition: “Security Engineering” Chapter 8 - Ross Anderson
- Database Schema for RBAC: “SQL Antipatterns” Chapter 14 - Bill Karwin
Difficulty: Beginner Time estimate: Weekend Prerequisites: Project 1, basic SQL or data modeling
Real world outcome:
from rbac import RBACEngine
engine = RBACEngine()
# Define permissions
engine.create_permission("article:read")
engine.create_permission("article:write")
engine.create_permission("article:delete")
engine.create_permission("user:manage")
# Define roles with permissions
engine.create_role("viewer", permissions=["article:read"])
engine.create_role("editor", permissions=["article:read", "article:write"])
engine.create_role("admin", permissions=["article:read", "article:write",
"article:delete", "user:manage"])
# Assign users to roles
engine.assign_role("alice", "editor")
engine.assign_role("bob", "viewer")
engine.assign_role("charlie", "admin")
# Check permissions
engine.can("alice", "article:read") # True (from editor role)
engine.can("alice", "article:delete") # False
engine.can("bob", "article:write") # False
engine.can("charlie", "user:manage") # True (from admin role)
# List user's effective permissions
engine.get_permissions("alice")
# Output: {'article:read', 'article:write'}
# Audit: Who has this permission?
engine.who_can("article:delete")
# Output: ['charlie']
Implementation Hints:
The classic RBAC data model:
┌─────────┐ ┌─────────────┐ ┌───────┐ ┌────────────────┐ ┌─────────────┐
│ Users │─────▶│ UserRoles │◀─────│ Roles │─────▶│ RolePermissions│◀─────│ Permissions │
└─────────┘ └─────────────┘ └───────┘ └────────────────┘ └─────────────┘
│ │
│ user_id, role_id │ role_id, permission_id
│ │
SQL Schema approach:
CREATE TABLE users (id, name, ...);
CREATE TABLE roles (id, name, description);
CREATE TABLE permissions (id, name, description);
CREATE TABLE user_roles (user_id, role_id);
CREATE TABLE role_permissions (role_id, permission_id);
The permission check becomes a join:
SELECT 1 FROM users u
JOIN user_roles ur ON u.id = ur.user_id
JOIN role_permissions rp ON ur.role_id = rp.role_id
JOIN permissions p ON rp.permission_id = p.id
WHERE u.id = ? AND p.name = ?
Consider:
- How do you handle users with multiple roles?
- What if two roles have conflicting permissions?
- How do you add role hierarchies (admin inherits from editor)?
Learning milestones:
- Users can be assigned roles → You understand identity binding
- Roles aggregate permissions → You understand the indirection layer
- Multi-role users work → You understand permission union
- Audit queries work → You can answer “who can do what?”
Project 3: Access Control List (ACL) Implementation
- File: LEARN_AUTHORIZATION_DEEP_DIVE.md
- Main Programming Language: Go
- Alternative Programming Languages: Python, Rust, C
- Coolness Level: Level 3: Genuinely Clever
- Business Potential: 1. The “Resume Gold”
- Difficulty: Level 2: Intermediate
- Knowledge Area: Object-Level Access Control
- Software or Tool: ACL Engine
- Main Book: “Operating Systems: Three Easy Pieces” by Arpaci-Dusseau
What you’ll build: A system where each resource (object) has an associated list of who can access it and how. Like how file systems store permissions per file.
Why it teaches authorization: ACLs are the foundation of discretionary access control. They show you how permissions are stored “with the object” rather than “with the user”, and the trade-offs this creates.
Core challenges you’ll face:
- ACL storage per resource → maps to understanding object-centric authorization
- ACL inheritance → maps to how folder permissions propagate to files
- ACL modification permissions → maps to who can change who has access
- Efficient checking with large ACLs → maps to performance in access control
Key Concepts:
- ACL Theory: “Operating Systems: Three Easy Pieces” Chapter 53 - Arpaci-Dusseau
- Capability vs ACL: “Security Engineering” Chapter 4 - Ross Anderson
- POSIX ACLs: “The Linux Programming Interface” Chapter 17 - Michael Kerrisk
Difficulty: Intermediate Time estimate: 1 week Prerequisites: Project 1, understanding of data structures
Real world outcome:
// Your ACL system in action
acl := NewACLEngine()
// Create resources with owners
acl.CreateResource("file:report.pdf", "alice")
acl.CreateResource("folder:shared", "alice")
acl.CreateResource("file:shared/data.csv", "alice")
// Set ACL entries
acl.SetACL("file:report.pdf", []ACLEntry{
{Principal: "alice", Permissions: []string{"read", "write", "delete", "share"}},
{Principal: "bob", Permissions: []string{"read"}},
})
acl.SetACL("folder:shared", []ACLEntry{
{Principal: "group:engineering", Permissions: []string{"read", "write"}},
})
// Check access
acl.Can("bob", "read", "file:report.pdf") // true
acl.Can("bob", "write", "file:report.pdf") // false
acl.Can("charlie", "read", "file:report.pdf") // false
// Inheritance: file in folder inherits folder ACL
acl.Can("dave", "read", "file:shared/data.csv") // true if dave in engineering group
// Get ACL for resource
acl.GetACL("file:report.pdf")
// Output:
// alice: [read, write, delete, share]
// bob: [read]
// Get all resources a user can access
acl.ListAccessible("bob")
// Output: [file:report.pdf (read)]
Implementation Hints:
ACLs are stored with each resource:
Resource: "file:report.pdf"
ACL:
├── alice: [read, write, delete, share]
├── bob: [read]
└── group:editors: [read, write]
Data structure:
type ACLEntry struct {
Principal string // user:alice, group:editors, role:admin
Permissions []string
Inherited bool // true if inherited from parent
}
type Resource struct {
ID string
Owner string
Parent string // for inheritance
ACL []ACLEntry
}
Key decisions:
- Inheritance strategy: Does a child resource inherit parent ACLs? Can it override?
- Group expansion: When checking access, do you expand groups to members?
- Owner privileges: Does the owner always have full access?
- Negative entries: Can you explicitly deny in an ACL?
Consider:
- What’s the difference between ACLs and capability lists?
- How do you handle circular inheritance?
- When should ACL changes propagate to children?
Learning milestones:
- Per-resource ACLs work → You understand object-centric permissions
- ACL inheritance works → You understand permission propagation
- Group expansion works → You understand principal abstraction
- Owner semantics work → You understand discretionary control
Project 4: Unix-Style File Permissions
- File: LEARN_AUTHORIZATION_DEEP_DIVE.md
- Main Programming Language: C
- Alternative Programming Languages: Rust, Go
- Coolness Level: Level 4: Hardcore Tech Flex
- Business Potential: 1. The “Resume Gold”
- Difficulty: Level 2: Intermediate
- Knowledge Area: Operating System Security
- Software or Tool: Virtual Filesystem
- Main Book: “The Linux Programming Interface” by Michael Kerrisk
What you’ll build: A virtual filesystem that implements Unix-style permissions (rwxrwxrwx), with owner/group/other distinctions, setuid/setgid bits, and sticky bit.
Why it teaches authorization: Unix permissions are a masterclass in compact, efficient authorization. Understanding how 9 bits can express a complete access control policy teaches you about trade-offs between simplicity and expressiveness.
Core challenges you’ll face:
- Permission bit encoding → maps to understanding compact permission representation
- Owner/group/other model → maps to understanding permission classes
- Setuid/setgid semantics → maps to privilege escalation mechanisms
- Directory vs file permissions → maps to context-dependent authorization
Key Concepts:
- Unix Permission Model: “The Linux Programming Interface” Chapter 15 - Michael Kerrisk
- Setuid Mechanics: “The Linux Programming Interface” Chapter 9 - Michael Kerrisk
- File System Security: “Operating Systems: Three Easy Pieces” Chapter 39 - Arpaci-Dusseau
Difficulty: Intermediate Time estimate: 1-2 weeks Prerequisites: Project 3, basic C, understanding of bits and binary
Real world outcome:
// Your virtual filesystem in action
VFS* fs = vfs_create();
// Create users and groups
vfs_add_user(fs, "alice", 1000, 1000); // uid=1000, gid=1000
vfs_add_user(fs, "bob", 1001, 1000); // uid=1001, same group as alice
vfs_add_group(fs, "staff", 1000);
// Create files with permissions
vfs_create_file(fs, "/home/alice/secret.txt", 1000, 1000, 0600); // rw-------
vfs_create_file(fs, "/shared/report.txt", 1000, 1000, 0644); // rw-r--r--
vfs_create_dir(fs, "/shared", 1000, 1000, 0755); // rwxr-xr-x
// Check access
vfs_can_access(fs, "alice", "/home/alice/secret.txt", R_OK); // 0 (success)
vfs_can_access(fs, "bob", "/home/alice/secret.txt", R_OK); // -1 (denied)
vfs_can_access(fs, "bob", "/shared/report.txt", R_OK); // 0 (other read)
// Demonstrate setuid
vfs_create_file(fs, "/usr/bin/passwd", 0, 0, 04755); // rwsr-xr-x
// When bob runs passwd, effective uid becomes 0 (root)
// Display permissions like ls -l
vfs_stat(fs, "/shared/report.txt");
// Output: -rw-r--r-- 1 alice staff 1024 Dec 21 10:00 report.txt
// Demonstrate sticky bit on /tmp
vfs_create_dir(fs, "/tmp", 0, 0, 01777); // drwxrwxrwt
// Anyone can write, but only owner can delete their own files
Implementation Hints:
The 12-bit permission model:
┌────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┐
│suid│sgid│stky│ r │ w │ x │ r │ w │ x │ r │ w │ x │
└────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┘
│ │ │ └────────┘ └────────┘ └────────┘
│ │ │ OWNER GROUP OTHER
│ │ └── Sticky bit (directories only)
│ └─────── SetGID
└──────────── SetUID
Permission check algorithm:
1. If user is root (uid=0), allow everything
2. If user is owner, check owner bits (bits 8,7,6)
3. If user is in file's group, check group bits (bits 5,4,3)
4. Otherwise, check other bits (bits 2,1,0)
For directories:
r= can list contents (readdir)w= can create/delete files (requires x too)x= can traverse (cd into, access files inside)
Questions to consider:
- Why does deleting a file require write permission on the directory, not the file?
- What security problem does the sticky bit solve?
- Why is setuid dangerous but sometimes necessary?
Learning milestones:
- Basic rwx works for owner/group/other → You understand permission classes
- Directory permissions work correctly → You understand context-dependent auth
- Setuid changes effective uid → You understand privilege escalation
- Sticky bit works on /tmp → You understand deletion authorization
Project 5: Claims-Based Authorization
- File: LEARN_AUTHORIZATION_DEEP_DIVE.md
- Main Programming Language: TypeScript
- Alternative Programming Languages: Go, Python, C#
- Coolness Level: Level 3: Genuinely Clever
- Business Potential: 2. The “Micro-SaaS / Pro Tool”
- Difficulty: Level 2: Intermediate
- Knowledge Area: Token-Based Authorization
- Software or Tool: Claims Authorization Middleware
- Main Book: “OAuth 2 in Action” by Justin Richer
What you’ll build: An authorization middleware that makes decisions based on claims in a JWT token—checking roles, scopes, custom claims, and claim combinations.
Why it teaches authorization: Modern web applications use tokens for stateless authorization. Understanding how claims carry authorization data and how to evaluate them is essential for building secure APIs.
Core challenges you’ll face:
- Claim extraction from JWT → maps to understanding token structure
- Claim-based policy evaluation → maps to declarative authorization rules
- Combining multiple claims → maps to boolean logic in authorization
- Claim freshness and revocation → maps to stateless vs stateful trade-offs
Key Concepts:
- JWT Structure: “OAuth 2 in Action” Chapter 11 - Justin Richer
- Claims-Based Identity: “Programming Windows Identity Foundation” Chapter 1 - Vittorio Bertocci
- Stateless Authorization: “Designing Data-Intensive Applications” Chapter 9 - Martin Kleppmann
Difficulty: Intermediate Time estimate: 1 week Prerequisites: Project 2, understanding of JWTs, HTTP middleware
Real world outcome:
// Your claims authorization middleware
import { ClaimsAuthorizer } from './claims-auth';
const authorizer = new ClaimsAuthorizer();
// Define policies
authorizer.policy('admin-only', claims =>
claims.has('role', 'admin')
);
authorizer.policy('can-edit-articles', claims =>
claims.has('role', 'admin') ||
claims.has('role', 'editor') ||
claims.has('scope', 'articles:write')
);
authorizer.policy('premium-feature', claims =>
claims.has('subscription', 'premium') &&
claims.get('subscription_expires') > Date.now()
);
authorizer.policy('own-resource', (claims, context) =>
claims.get('sub') === context.resourceOwnerId
);
// Use in Express
app.delete('/articles/:id',
authorizer.require('can-edit-articles'),
async (req, res) => {
// Only reached if authorized
}
);
app.put('/users/:id',
authorizer.require('own-resource', { resourceOwnerId: req.params.id }),
async (req, res) => {
// Only the user themselves can update their profile
}
);
// Evaluate programmatically
const claims = parseJWT(token);
const result = authorizer.evaluate('premium-feature', claims);
// { allowed: false, reason: 'subscription_expires claim is in the past' }
Implementation Hints:
JWT claims are just key-value pairs:
{
"sub": "user123",
"iss": "auth.example.com",
"aud": "api.example.com",
"exp": 1735300000,
"iat": 1735200000,
"role": "editor",
"scope": "read write",
"subscription": "premium",
"department": "engineering"
}
Policy evaluation:
type ClaimsContext = {
has(claim: string, value?: string): boolean;
get(claim: string): any;
all(): Record<string, any>;
};
type Policy = (claims: ClaimsContext, context?: any) => boolean;
// Evaluation with explanation
function evaluate(policy: Policy, claims: Claims): AuthResult {
try {
const allowed = policy(claims);
return { allowed, reason: allowed ? 'Policy satisfied' : 'Policy denied' };
} catch (e) {
return { allowed: false, reason: e.message };
}
}
Consider:
- How do you handle missing claims vs null claims?
- How do you combine policies (AND, OR)?
- How do you audit which policy caused a denial?
- What happens when a token is valid but expired?
Learning milestones:
- Extract claims from JWT → You understand token structure
- Simple claim checks work → You understand basic policy evaluation
- Complex boolean policies work → You understand policy composition
- Context-aware policies work → You understand resource-based authorization
Project 6: Hierarchical Permission System
- File: LEARN_AUTHORIZATION_DEEP_DIVE.md
- Main Programming Language: Python
- Alternative Programming Languages: Go, Java, TypeScript
- Coolness Level: Level 3: Genuinely Clever
- Business Potential: 3. The “Service & Support” Model
- Difficulty: Level 2: Intermediate
- Knowledge Area: Permission Inheritance
- Software or Tool: Hierarchical RBAC Engine
- Main Book: “Role-Based Access Control” by Ferraiolo, Kuhn, Chandramouli
What you’ll build: An RBAC system where roles inherit from other roles, permissions inherit through resource hierarchies, and you can query the effective permissions considering all inheritance paths.
Why it teaches authorization: Real organizations have hierarchical structures. Understanding how permissions flow through role hierarchies (Admin > Manager > Employee) and resource hierarchies (Company > Department > Project) is essential for enterprise authorization.
Core challenges you’ll face:
- Role inheritance → maps to transitive permission calculation
- Resource hierarchies → maps to permission propagation down trees
- Cycle detection → maps to preventing infinite loops in inheritance
- Effective permission calculation → maps to merging permissions from multiple sources
Key Concepts:
- RBAC Hierarchies: “Role-Based Access Control” Chapter 4 - Ferraiolo et al.
- Graph Algorithms for Inheritance: “Algorithms” Chapter 4 - Sedgewick
- Organizational Access Control: “Enterprise Security Architecture” Chapter 8 - Sherwood
Difficulty: Intermediate Time estimate: 1-2 weeks Prerequisites: Project 2, graph theory basics
Real world outcome:
from hierarchical_rbac import HierarchicalRBAC
rbac = HierarchicalRBAC()
# Define role hierarchy
# admin
# / \
# manager auditor
# |
# employee
rbac.create_role("admin")
rbac.create_role("manager", inherits_from=["admin"])
rbac.create_role("auditor", inherits_from=["admin"])
rbac.create_role("employee", inherits_from=["manager"])
# Permissions at each level
rbac.grant_to_role("employee", "project:read")
rbac.grant_to_role("manager", "project:write", "project:delete")
rbac.grant_to_role("admin", "system:configure")
rbac.grant_to_role("auditor", "audit:read")
# Resource hierarchy
# /company
# /company/engineering
# /company/engineering/project-x
# /company/sales
rbac.create_resource("/company")
rbac.create_resource("/company/engineering", parent="/company")
rbac.create_resource("/company/engineering/project-x", parent="/company/engineering")
# Grant access at different levels
rbac.grant("/company", "alice", "admin") # Alice is admin of whole company
rbac.grant("/company/engineering", "bob", "manager") # Bob manages engineering
rbac.grant("/company/engineering/project-x", "charlie", "employee") # Charlie on project-x
# Check permissions (considering all inheritance)
rbac.can("alice", "system:configure", "/company/sales") # True (admin at /company)
rbac.can("bob", "project:write", "/company/engineering/project-x") # True (manager + inheritance)
rbac.can("charlie", "project:delete", "/company/engineering/project-x") # False (only employee)
# Get effective permissions
rbac.effective_permissions("bob", "/company/engineering/project-x")
# Output: {'project:read', 'project:write', 'project:delete'}
# Explain why
rbac.explain("bob", "project:write", "/company/engineering/project-x")
# Output:
# ALLOWED because:
# - bob has role 'manager' at '/company/engineering'
# - '/company/engineering/project-x' inherits from '/company/engineering'
# - role 'manager' has permission 'project:write'
Implementation Hints:
Two hierarchies to manage:
Role Hierarchy (Inheritance goes UP):
admin
/ \
manager auditor
|
employee
Resource Hierarchy (Inheritance goes DOWN):
/company
├── /company/engineering
│ └── /company/engineering/project-x
└── /company/sales
Algorithm for permission check:
def can(user, permission, resource):
# Walk up resource hierarchy
for r in resource_and_ancestors(resource):
# Get user's roles at this resource level
for role in get_roles(user, r):
# Walk up role hierarchy
for inherited_role in role_and_ancestors(role):
if has_permission(inherited_role, permission):
return True
return False
Use topological sort to detect cycles in role hierarchy.
Consider:
- Can a child resource revoke a permission granted by parent?
- Should role inheritance be transitive or single-level?
- How do you handle conflicting permissions from multiple inheritance paths?
Learning milestones:
- Role inheritance works → You understand transitive roles
- Resource inheritance works → You understand permission propagation
- Combined inheritance works → You understand complex permission resolution
- Cycle detection works → You understand graph validation
Project 7: Multi-Tenant Authorization System
- File: LEARN_AUTHORIZATION_DEEP_DIVE.md
- Main Programming Language: Go
- Alternative Programming Languages: Python, TypeScript, Java
- Coolness Level: Level 4: Hardcore Tech Flex
- Business Potential: 4. The “Open Core” Infrastructure
- Difficulty: Level 3: Advanced
- Knowledge Area: SaaS Authorization
- Software or Tool: Multi-Tenant AuthZ Engine
- Main Book: “Building Multi-Tenant SaaS Architectures” by Tod Golding
What you’ll build: An authorization system that completely isolates tenants (organizations) while supporting tenant-specific roles, cross-tenant sharing, and tenant hierarchies (parent orgs with sub-orgs).
Why it teaches authorization: Every SaaS application needs multi-tenancy. Understanding how to ensure Tenant A can never see Tenant B’s data, while still allowing controlled cross-tenant collaboration, is critical for building secure SaaS products.
Core challenges you’ll face:
- Tenant isolation → maps to fundamental SaaS security
- Tenant-specific roles → maps to per-tenant customization
- Cross-tenant sharing → maps to controlled boundary crossing
- Tenant hierarchies → maps to enterprise customers with sub-organizations
Key Concepts:
- Multi-Tenant Patterns: “Building Multi-Tenant SaaS Architectures” Chapter 5 - Tod Golding
- Tenant Isolation: “AWS Well-Architected SaaS Lens” - Isolation Section
- Data Partitioning: “Designing Data-Intensive Applications” Chapter 6 - Kleppmann
Difficulty: Advanced Time estimate: 2-3 weeks Prerequisites: Projects 2 and 6, understanding of SaaS architecture
Real world outcome:
// Your multi-tenant authorization system
mt := NewMultiTenantAuthZ()
// Create tenants
mt.CreateTenant("acme-corp", nil) // Root tenant
mt.CreateTenant("acme-europe", "acme-corp") // Sub-tenant
mt.CreateTenant("globex", nil) // Another root tenant
// Tenant-specific roles (Acme has custom roles)
mt.CreateRole("acme-corp", "super-admin", []string{"*"})
mt.CreateRole("acme-corp", "regional-manager", []string{"read", "write", "manage-users"})
// Default roles for Globex (uses system defaults)
mt.UseDefaultRoles("globex") // admin, member, guest
// Users belong to tenants
mt.AddUserToTenant("alice", "acme-corp", "super-admin")
mt.AddUserToTenant("bob", "acme-europe", "regional-manager")
mt.AddUserToTenant("charlie", "globex", "admin")
// Authorization checks include tenant context
mt.Can("alice", "manage-users", "acme-corp", "user:123") // true
mt.Can("alice", "manage-users", "acme-europe", "user:123") // true (parent tenant)
mt.Can("alice", "read", "globex", "document:456") // false (wrong tenant!)
mt.Can("bob", "read", "acme-europe", "report:789") // true
mt.Can("bob", "read", "acme-corp", "report:001") // false (can't access parent)
// Cross-tenant sharing (explicit grants)
mt.ShareResource("globex", "document:public-spec", "acme-corp", "read")
mt.Can("alice", "read", "acme-corp", "document:public-spec@globex") // true (shared)
// List accessible tenants for user
mt.GetUserTenants("alice")
// Output: [{tenant: "acme-corp", role: "super-admin"},
// {tenant: "acme-europe", role: "super-admin (inherited)"]}
// Tenant-level resource listing (never leaks across tenants)
mt.ListResources("acme-corp", "alice", "document")
// Only returns documents in acme-corp and sub-tenants
Implementation Hints:
Core data model:
Tenants:
- id: "acme-corp"
- parent_id: null
- settings: {...}
- id: "acme-europe"
- parent_id: "acme-corp"
- settings: {...}
TenantMemberships:
- user_id, tenant_id, role_id
TenantRoles:
- id, tenant_id (null for system roles), permissions[]
Resources:
- id, tenant_id, owner_id, type, ...
CrossTenantShares:
- resource_id, from_tenant, to_tenant, permissions[]
The critical rule: Every permission check must include tenant context
func (mt *MultiTenantAuthZ) Can(user, action, tenant, resource string) bool {
// 1. Verify user belongs to tenant (or parent)
membership := mt.GetMembership(user, tenant)
if membership == nil {
return false // Not a member of this tenant
}
// 2. Verify resource belongs to tenant (or is shared)
resourceTenant := mt.GetResourceTenant(resource)
if resourceTenant != tenant && !mt.IsShared(resource, tenant) {
return false // Resource not in tenant
}
// 3. Check role permissions
return mt.RoleHasPermission(membership.Role, action)
}
Consider:
- How do you handle users who belong to multiple tenants?
- How do you prevent tenant ID spoofing in API requests?
- What happens when a tenant is deleted?
Learning milestones:
- Tenant isolation is bulletproof → You understand SaaS security fundamentals
- Tenant hierarchies work → You understand enterprise organization structures
- Cross-tenant sharing works safely → You understand controlled boundary crossing
- Per-tenant roles work → You understand customization without complexity
Project 8: Attribute-Based Access Control (ABAC) System
- File: LEARN_AUTHORIZATION_DEEP_DIVE.md
- Main Programming Language: Go
- Alternative Programming Languages: Python, Rust, Java
- Coolness Level: Level 4: Hardcore Tech Flex
- Business Potential: 4. The “Open Core” Infrastructure
- Difficulty: Level 3: Advanced
- Knowledge Area: Policy-Based Authorization
- Software or Tool: ABAC Policy Engine
- Main Book: “Attribute-Based Access Control” by Hu, Kuhn, Ferraiolo
What you’ll build: A policy engine that evaluates access based on attributes of the subject (user), resource, action, and environment (time, location, device)—not just roles.
Why it teaches authorization: ABAC is the most flexible authorization model. It can express any policy that RBAC can, plus complex rules like “Doctors can view patient records only during their shift, at the hospital, for patients assigned to them.” Understanding ABAC prepares you for real-world complexity.
Core challenges you’ll face:
- Attribute collection from multiple sources → maps to integrating identity, resource, and context data
- Policy language design → maps to expressing complex boolean conditions
- Policy evaluation performance → maps to optimizing rule matching
- Policy conflict resolution → maps to combining multiple applicable policies
Key Concepts:
- ABAC Model: “Attribute-Based Access Control” Chapter 2 - Hu, Kuhn, Ferraiolo
- XACML Architecture: OASIS XACML 3.0 Specification - Section 3
- Policy Combining Algorithms: “Attribute-Based Access Control” Chapter 5 - Hu et al.
Difficulty: Advanced Time estimate: 2-3 weeks Prerequisites: Projects 5 and 6, understanding of policy languages
Real world outcome:
// Your ABAC engine
abac := NewABACEngine()
// Define attribute providers
abac.RegisterAttributeProvider("user", UserAttributeProvider{})
abac.RegisterAttributeProvider("resource", ResourceAttributeProvider{})
abac.RegisterAttributeProvider("environment", EnvironmentAttributeProvider{})
// Define policies
abac.AddPolicy(Policy{
ID: "doctor-patient-access",
Target: Target{
SubjectMatch: Attr("user.role").Equals("doctor"),
ResourceMatch: Attr("resource.type").Equals("medical_record"),
ActionMatch: Attr("action").In("read", "write"),
},
Condition: And(
Attr("user.department").Equals(Attr("resource.department")),
Attr("resource.patient_id").In(Attr("user.assigned_patients")),
Attr("environment.time").Between("08:00", "20:00"),
Attr("environment.location").Equals("hospital_network"),
),
Effect: "permit",
})
abac.AddPolicy(Policy{
ID: "emergency-override",
Target: Target{
SubjectMatch: Attr("user.role").In("doctor", "nurse"),
ResourceMatch: Attr("resource.type").Equals("medical_record"),
},
Condition: And(
Attr("environment.emergency_declared").Equals(true),
),
Effect: "permit",
Priority: 100, // Higher priority overrides other denies
})
abac.AddPolicy(Policy{
ID: "default-deny",
Target: Target{}, // Matches everything
Condition: nil,
Effect: "deny",
Priority: -1, // Lowest priority
})
// Request evaluation
request := Request{
Subject: map[string]any{
"id": "dr-smith",
"role": "doctor",
"department": "cardiology",
"assigned_patients": []string{"patient-123", "patient-456"},
},
Resource: map[string]any{
"id": "record-789",
"type": "medical_record",
"patient_id": "patient-123",
"department": "cardiology",
},
Action: "read",
Environment: map[string]any{
"time": "14:30",
"location": "hospital_network",
"emergency_declared": false,
},
}
result := abac.Evaluate(request)
// {
// Decision: "permit",
// AppliedPolicy: "doctor-patient-access",
// EvaluatedPolicies: [...],
// Obligations: [...],
// }
Implementation Hints:
ABAC architecture (based on XACML):
┌─────────────────┐
│ Policy Decision │
│ Point (PDP) │
└────────▲────────┘
│
┌───────────────────────┼───────────────────────┐
│ │ │
┌────▼────┐ ┌─────▼────┐ ┌────▼────┐
│ Policy │ │ Request │ │Attribute│
│ Retrieval│ │ Context │ │ Finder │
│ Point │ │ │ │ │
└─────────┘ └──────────┘ └────┬────┘
│
┌────▼────┐
│ User DB │
│ Resource│
│ Context │
└─────────┘
Policy evaluation algorithm:
1. Find all policies whose Target matches the request
2. For each matching policy, evaluate its Condition
3. Collect all applicable policy effects (permit/deny)
4. Apply combining algorithm:
- deny-overrides: any deny = deny
- permit-overrides: any permit = permit
- first-applicable: first matching policy wins
- highest-priority: policy with highest priority wins
5. Return decision with explanation
Consider:
- How do you handle missing attributes?
- How do you optimize for policies with many conditions?
- How do you audit which attributes were used in a decision?
Learning milestones:
- Simple attribute comparisons work → You understand attribute evaluation
- Complex conditions work → You understand boolean composition
- Multiple policies combine correctly → You understand policy resolution
- Dynamic attributes work → You understand real-time context
Project 9: Row-Level Security Engine
- File: LEARN_AUTHORIZATION_DEEP_DIVE.md
- Main Programming Language: Go
- Alternative Programming Languages: Python, Rust
- Coolness Level: Level 4: Hardcore Tech Flex
- Business Potential: 4. The “Open Core” Infrastructure
- Difficulty: Level 3: Advanced
- Knowledge Area: Database Security
- Software or Tool: RLS Query Rewriter
- Main Book: “Database Internals” by Alex Petrov
What you’ll build: A query rewriting layer that automatically filters database queries based on the current user’s permissions—ensuring users only see rows they’re authorized to access.
Why it teaches authorization: Row-level security is authorization at the data layer. It’s the last line of defense ensuring that even if application code has bugs, the database itself enforces access rules. Understanding RLS teaches you about pushing authorization into the data tier.
Core challenges you’ll face:
- Query parsing and rewriting → maps to understanding SQL AST manipulation
- Policy-to-predicate translation → maps to expressing authorization as WHERE clauses
- Performance with RLS predicates → maps to indexing for filtered queries
- Handling joins and subqueries → maps to complex query authorization
Key Concepts:
- PostgreSQL RLS: PostgreSQL Documentation - Chapter 5.8
- Query Rewriting: “Database Internals” Chapter 3 - Alex Petrov
- SQL Injection Prevention: “SQL Antipatterns” Chapter 20 - Bill Karwin
Difficulty: Advanced Time estimate: 2-3 weeks Prerequisites: Project 8, SQL parsing knowledge
Real world outcome:
// Your RLS engine wrapping database queries
rls := NewRLSEngine(db)
// Define row-level policies
rls.AddPolicy("documents", Policy{
Name: "own_documents",
Using: "owner_id = current_user_id()",
Check: "owner_id = current_user_id()", // For INSERT/UPDATE
Roles: []string{"user"},
})
rls.AddPolicy("documents", Policy{
Name: "shared_documents",
Using: "id IN (SELECT document_id FROM shares WHERE user_id = current_user_id())",
Roles: []string{"user"},
})
rls.AddPolicy("documents", Policy{
Name: "admin_all",
Using: "true", // Admins see everything
Roles: []string{"admin"},
})
// Set context for current request
ctx := rls.WithUser(context.Background(), User{
ID: "user-123",
Roles: []string{"user"},
})
// Queries are automatically rewritten
// Original: SELECT * FROM documents
// Rewritten: SELECT * FROM documents
// WHERE (owner_id = 'user-123'
// OR id IN (SELECT document_id FROM shares WHERE user_id = 'user-123'))
rows, _ := rls.Query(ctx, "SELECT * FROM documents WHERE status = 'active'")
// Only returns documents user-123 owns or has been shared with them
// Explain what happened
rls.Explain(ctx, "SELECT * FROM documents")
// Output:
// Original query: SELECT * FROM documents
// Applied policies: own_documents, shared_documents
// Rewritten query: SELECT * FROM documents WHERE (owner_id = $1 OR id IN (...))
// Parameters: ['user-123']
// Insert respects Check policy
err := rls.Exec(ctx, "INSERT INTO documents (title, owner_id) VALUES ('test', 'other-user')")
// Error: new row violates row-level security policy "own_documents"
Implementation Hints:
RLS works by rewriting queries:
Original:
SELECT * FROM orders WHERE status = 'pending'
With RLS policy "user sees own orders":
SELECT * FROM orders
WHERE status = 'pending'
AND (customer_id = current_user())
With RLS policy "admin sees all":
SELECT * FROM orders
WHERE status = 'pending'
AND (true) -- effectively no filter
Query rewriting algorithm:
1. Parse SQL into AST
2. Find all table references in FROM/JOIN clauses
3. For each table, find applicable RLS policies
4. Combine policies with OR (any policy allows = allowed)
5. Add combined predicate to WHERE clause
6. For INSERT/UPDATE, also check the CHECK predicate
7. Serialize AST back to SQL
Use a SQL parser library (e.g., sqlparser-rs for Rust, pg_query for PostgreSQL AST).
Consider:
- How do you handle complex joins where both tables have RLS?
- How do you prevent SQL injection in policy predicates?
- How do you ensure indexes are used despite RLS predicates?
Learning milestones:
- Simple SELECT rewriting works → You understand query transformation
- Multiple tables with RLS work → You understand join authorization
- INSERT/UPDATE checking works → You understand write authorization
- Performance is acceptable → You understand query planning with RLS
Project 10: OAuth 2.0 Scope-Based Authorization
- File: LEARN_AUTHORIZATION_DEEP_DIVE.md
- Main Programming Language: Go
- Alternative Programming Languages: Python, TypeScript, Rust
- Coolness Level: Level 3: Genuinely Clever
- Business Potential: 3. The “Service & Support” Model
- Difficulty: Level 2: Intermediate
- Knowledge Area: OAuth / API Authorization
- Software or Tool: OAuth 2.0 Authorization Server
- Main Book: “OAuth 2 in Action” by Justin Richer
What you’ll build: An OAuth 2.0 authorization server that issues tokens with scopes, and a resource server that validates tokens and enforces scope-based access to API endpoints.
Why it teaches authorization: OAuth scopes are how third-party applications get limited access to your resources. Understanding how to design scopes, request consent, and enforce them teaches you about delegated authorization.
Core challenges you’ll face:
- Scope design → maps to granularity of delegated permissions
- Token issuance with requested scopes → maps to scope downgrading
- Scope validation at resource server → maps to enforcement at API boundaries
- Scope consent UI → maps to user-understandable permission requests
Key Concepts:
- OAuth 2.0 Scopes: “OAuth 2 in Action” Chapter 3 - Justin Richer
- Scope Design Patterns: IETF RFC 6749 Section 3.3
- API Authorization: “API Security in Action” Chapter 5 - Neil Madden
Difficulty: Intermediate Time estimate: 1-2 weeks Prerequisites: Project 5, understanding of OAuth 2.0 flow
Real world outcome:
// Your OAuth Authorization Server
authServer := NewOAuthServer()
// Register scopes
authServer.RegisterScope("profile:read", "Read your profile information")
authServer.RegisterScope("profile:write", "Update your profile")
authServer.RegisterScope("documents:read", "Read your documents")
authServer.RegisterScope("documents:write", "Create and update documents")
authServer.RegisterScope("documents:delete", "Delete documents")
authServer.RegisterScope("admin", "Full administrative access")
// Scope hierarchies (admin includes all)
authServer.SetScopeHierarchy("admin", []string{
"profile:read", "profile:write",
"documents:read", "documents:write", "documents:delete",
})
// Register client with allowed scopes
authServer.RegisterClient(Client{
ID: "mobile-app",
Secret: "...",
AllowedScopes: []string{"profile:read", "profile:write", "documents:read"},
// Note: mobile-app cannot request documents:write or admin
})
// Authorization request (user consent)
// GET /authorize?client_id=mobile-app&scope=profile:read%20documents:read&...
// User sees:
// "Mobile App wants to:
// ✓ Read your profile information
// ✓ Read your documents
// [Allow] [Deny]"
// Token issued with granted scopes
token := authServer.IssueToken("mobile-app", "user-123", []string{"profile:read", "documents:read"})
// Token contains: {"scope": "profile:read documents:read", "sub": "user-123", ...}
// Resource server validates scopes
resourceServer := NewResourceServer(authServer.PublicKey())
// Endpoint: GET /api/profile
resourceServer.RequireScope("profile:read")
func(r *http.Request) {
token := resourceServer.ValidateToken(r)
// Succeeds for mobile-app tokens with profile:read
}
// Endpoint: DELETE /api/documents/:id
resourceServer.RequireScope("documents:delete")
func(r *http.Request) {
token := resourceServer.ValidateToken(r)
// Fails for mobile-app tokens (doesn't have documents:delete)
// Returns 403 Forbidden with WWW-Authenticate header
}
// Endpoint requiring multiple scopes
resourceServer.RequireAllScopes("profile:read", "documents:write")
func(r *http.Request) {
// Must have BOTH scopes
}
Implementation Hints:
OAuth scope flow:
1. Client requests scopes: scope=profile:read documents:read
2. Authorization server checks: Are these scopes in client's allowed list?
3. User consents to scopes (or subset)
4. Token issued with granted scopes (may be less than requested)
5. Resource server reads scope claim from token
6. Each endpoint declares required scopes
7. Access granted only if token has required scopes
Scope validation:
func (rs *ResourceServer) RequireScope(scope string) Middleware {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
token := rs.extractToken(r)
if token == nil {
rs.unauthorized(w, "missing token")
return
}
if !rs.hasScope(token, scope) {
rs.forbidden(w, "insufficient_scope", scope)
return
}
next.ServeHTTP(w, r)
})
}
}
Consider:
- How do you handle scope upgrade requests after initial consent?
- How do you scope hierarchy without exposing it to clients?
- How do you audit which scopes are actually being used?
Learning milestones:
- Scopes are issued in tokens → You understand scope granting
- Scopes are enforced at endpoints → You understand scope checking
- Client scope restrictions work → You understand scope limiting
- Scope hierarchies work → You understand scope inheritance
Project 11: Policy Engine (OPA-Style)
- File: LEARN_AUTHORIZATION_DEEP_DIVE.md
- Main Programming Language: Go
- Alternative Programming Languages: Rust, Python
- Coolness Level: Level 5: Pure Magic (Super Cool)
- Business Potential: 5. The “Industry Disruptor”
- Difficulty: Level 4: Expert
- Knowledge Area: Policy Languages / Compilers
- Software or Tool: Policy Engine
- Main Book: “Engineering a Compiler” by Cooper & Torczon
What you’ll build: A policy engine with its own domain-specific language for writing authorization policies, a compiler/interpreter for that language, and an evaluation engine that runs policies against input data.
Why it teaches authorization: OPA (Open Policy Agent) has become the standard for cloud-native authorization. Building your own policy engine teaches you how authorization can be expressed as code, decoupled from applications, and evaluated uniformly across services.
Core challenges you’ll face:
- Policy language design → maps to expressing authorization rules declaratively
- Parser implementation → maps to understanding language processing
- Evaluation engine → maps to running policies efficiently
- Policy testing → maps to verifying authorization correctness
Key Concepts:
- OPA Architecture: Open Policy Agent Documentation - Philosophy
- Domain-Specific Languages: “Domain Specific Languages” Chapter 2 - Martin Fowler
- Interpreter Patterns: “Crafting Interpreters” Chapters 4-8 - Bob Nystrom
Difficulty: Expert Time estimate: 1 month+ Prerequisites: Projects 8 and 10, compiler basics
Real world outcome:
// Your policy engine
engine := NewPolicyEngine()
// Load policies (in your policy language)
engine.LoadPolicy("authz.policy", `
package authz
default allow = false
# Admin can do anything
allow {
input.user.role == "admin"
}
# Users can read their own resources
allow {
input.action == "read"
input.resource.owner == input.user.id
}
# Managers can read resources in their department
allow {
input.action == "read"
input.user.role == "manager"
input.resource.department == input.user.department
}
# Users can write if resource is draft and they own it
allow {
input.action == "write"
input.resource.status == "draft"
input.resource.owner == input.user.id
}
# Explain why something was allowed
reasons[msg] {
input.user.role == "admin"
msg := "User is admin"
}
reasons[msg] {
input.resource.owner == input.user.id
msg := sprintf("User %v owns resource", [input.user.id])
}
`)
// Evaluate policy
result := engine.Evaluate("authz.allow", map[string]any{
"user": map[string]any{
"id": "alice",
"role": "manager",
"department": "engineering",
},
"action": "read",
"resource": map[string]any{
"id": "doc-123",
"owner": "bob",
"department": "engineering",
"status": "published",
},
})
// result.Allowed: true
// result.Reason: "Managers can read resources in their department"
// Test policies
engine.Test(`
test_admin_allows_all {
allow with input as {
"user": {"role": "admin"},
"action": "delete",
"resource": {"owner": "anyone"}
}
}
test_non_owner_cannot_write {
not allow with input as {
"user": {"id": "alice", "role": "user"},
"action": "write",
"resource": {"owner": "bob", "status": "draft"}
}
}
`)
// Output: PASS: 2/2 tests passed
Implementation Hints:
Policy engine architecture:
┌─────────────────────────────────────────────────────────────┐
│ Policy Engine │
├──────────────┬──────────────────┬────────────────────────────┤
│ │ │ │
│ ┌────────┐ │ ┌────────────┐ │ ┌──────────────────────┐ │
│ │ Parser │──│─▶│ Compiler/ │──│─▶│ Evaluation Engine │ │
│ │ │ │ │ Optimizer │ │ │ │ │
│ └────────┘ │ └────────────┘ │ │ - Pattern matching │ │
│ │ │ │ │ - Unification │ │
│ policy.txt │ ┌────▼─────┐ │ │ - Backtracking │ │
│ │ │ IR / AST │ │ └──────────────────────┘ │
│ │ └──────────┘ │ │ │
│ │ │ ▼ │
│ │ │ ┌─────────────┐ │
│ │ │ │ Input (JSON)│ │
│ │ │ └─────────────┘ │
│ │ │ │ │
│ │ │ ▼ │
│ │ │ ┌─────────────┐ │
│ │ │ │ Result │ │
│ │ │ └─────────────┘ │
└──────────────┴──────────────────┴────────────────────────────┘
Start simple:
- Design a minimal policy language (rules, conditions, variables)
- Write a recursive descent parser
- Build an AST representation
- Write a tree-walking interpreter
- Add policy testing capabilities
- Optimize with partial evaluation
OPA’s Rego language features you might implement:
- Rules with multiple conditions (AND within rule)
- Multiple rules for same output (OR across rules)
- Pattern matching on input
- Comprehensions (list/set/object comprehensions)
- Built-in functions (string ops, comparison, etc.)
Consider:
- How do you handle undefined values vs false?
- How do you optimize for frequently evaluated policies?
- How do you debug why a policy denied access?
Learning milestones:
- Parser produces valid AST → You understand language processing
- Simple policies evaluate correctly → You understand rule evaluation
- Complex policies with variables work → You understand unification
- Policy tests pass → You can verify authorization correctness
Project 12: Zanzibar-Style Relationship-Based Access Control
- File: LEARN_AUTHORIZATION_DEEP_DIVE.md
- Main Programming Language: Go
- Alternative Programming Languages: Rust, Java
- Coolness Level: Level 5: Pure Magic (Super Cool)
- Business Potential: 5. The “Industry Disruptor”
- Difficulty: Level 5: Master
- Knowledge Area: Graph-Based Authorization
- Software or Tool: ReBAC Engine (Zanzibar Clone)
- Main Book: “Google Zanzibar Paper” (USENIX ATC 2019)
What you’ll build: A relationship-based access control system inspired by Google’s Zanzibar—storing relationships as tuples, defining permission rules that traverse the relationship graph, and checking permissions with graph traversal.
Why it teaches authorization: Zanzibar is how Google authorizes billions of requests per second for Drive, Docs, YouTube, and more. It represents the state of the art in scalable authorization. Understanding ReBAC teaches you how complex permissions emerge from simple relationships.
Core challenges you’ll face:
- Relationship tuple storage → maps to graph database design
- Permission rule language → maps to defining derived permissions
- Graph traversal for permission checks → maps to efficient recursive queries
- Consistency in distributed settings → maps to Zanzibar’s “Zookie” tokens
Key Concepts:
- Zanzibar Paper: “Zanzibar: Google’s Consistent, Global Authorization System” - USENIX ATC 2019
- Graph Databases: “Graph Databases” Chapter 4 - Robinson, Webber, Eifrem
- Recursive Queries: “Database Internals” Chapter 7 - Alex Petrov
Difficulty: Master Time estimate: 1-2 months Prerequisites: Projects 8 and 11, graph algorithms
Real world outcome:
// Your Zanzibar-style system (similar to SpiceDB, Ory Keto, AuthZed)
z := NewZanzibar()
// Define schema (types and relations)
z.WriteSchema(`
definition user {}
definition group {
relation member: user
}
definition folder {
relation owner: user
relation viewer: user | group#member
relation parent: folder
permission view = viewer + owner + parent->view
permission edit = owner + parent->edit
permission delete = owner
}
definition document {
relation owner: user
relation editor: user | group#member
relation viewer: user | group#member
relation parent: folder
permission view = viewer + editor + owner + parent->view
permission edit = editor + owner + parent->edit
permission delete = owner
}
`)
// Write relationships (tuples)
z.WriteRelationships([]Relationship{
// Groups
{Resource: "group:engineering", Relation: "member", Subject: "user:alice"},
{Resource: "group:engineering", Relation: "member", Subject: "user:bob"},
// Folder structure
{Resource: "folder:root", Relation: "owner", Subject: "user:admin"},
{Resource: "folder:eng", Relation: "parent", Subject: "folder:root"},
{Resource: "folder:eng", Relation: "viewer", Subject: "group:engineering#member"},
// Documents
{Resource: "document:design-doc", Relation: "parent", Subject: "folder:eng"},
{Resource: "document:design-doc", Relation: "owner", Subject: "user:alice"},
{Resource: "document:secret", Relation: "owner", Subject: "user:admin"},
})
// Check permissions
z.Check("user:alice", "view", "document:design-doc") // true (owner)
z.Check("user:bob", "view", "document:design-doc") // true (eng group->folder:eng->parent->view)
z.Check("user:bob", "edit", "document:design-doc") // false
z.Check("user:alice", "view", "document:secret") // false (not in any relation path)
// Expand: Show all subjects with permission
z.Expand("view", "document:design-doc")
// Output:
// {
// union: [
// {leaf: {users: ["user:alice"]}}, // owner
// {leaf: {users: ["user:bob", "user:alice"]}}, // via group->folder
// ]
// }
// List: What can this user access?
z.ListObjects("user:bob", "view", "document")
// Output: ["document:design-doc"]
// Watch: Stream changes
z.Watch(func(change RelationshipChange) {
log.Printf("Relationship %s: %v", change.Type, change.Relationship)
})
Implementation Hints:
The Zanzibar model:
Namespace (Type) Definition:
document {
relation owner: user
relation viewer: user | group#member
relation parent: folder
permission view = viewer + owner + parent->view
}
Relationships (Tuples):
(document:123, owner, user:alice)
(document:123, parent, folder:456)
(folder:456, viewer, group:eng#member)
(group:eng, member, user:bob)
Check(user:bob, view, document:123):
1. Is user:bob a direct viewer of document:123? No
2. Is user:bob the owner of document:123? No
3. Does document:123 have a parent? Yes: folder:456
4. Check(user:bob, view, folder:456):
a. Is user:bob a direct viewer of folder:456?
- group:eng#member is a viewer
- Is user:bob a member of group:eng? Yes!
b. Return true
5. Return true
Graph traversal algorithm:
func (z *Zanzibar) Check(subject, permission, resource string) bool {
// Get type definition
typeDef := z.GetType(resourceType(resource))
permDef := typeDef.Permissions[permission]
// Evaluate permission expression
return z.evaluateExpr(permDef.Expression, subject, resource)
}
func (z *Zanzibar) evaluateExpr(expr Expr, subject, resource string) bool {
switch e := expr.(type) {
case *UnionExpr: // +
return z.evaluateExpr(e.Left, subject, resource) ||
z.evaluateExpr(e.Right, subject, resource)
case *IntersectExpr: // &
return z.evaluateExpr(e.Left, subject, resource) &&
z.evaluateExpr(e.Right, subject, resource)
case *RelationRef: // viewer
return z.hasRelation(resource, e.Relation, subject)
case *ArrowExpr: // parent->view
parents := z.getRelated(resource, e.Relation)
for _, parent := range parents {
if z.Check(subject, e.Permission, parent) {
return true
}
}
return false
}
}
Consider:
- How do you handle cycles in the relationship graph?
- How do you cache permission checks?
- How do you ensure consistency during relationship updates?
Learning milestones:
- Direct relationships work → You understand tuple storage
- Computed permissions work → You understand permission expressions
- Arrow expressions traverse graph → You understand recursive authorization
- Expand shows all paths → You can debug complex permissions
Project 13: Distributed Authorization Service
- File: LEARN_AUTHORIZATION_DEEP_DIVE.md
- Main Programming Language: Go
- Alternative Programming Languages: Rust, Java
- Coolness Level: Level 4: Hardcore Tech Flex
- Business Potential: 4. The “Open Core” Infrastructure
- Difficulty: Level 4: Expert
- Knowledge Area: Distributed Systems
- Software or Tool: Distributed AuthZ Service
- Main Book: “Designing Data-Intensive Applications” by Martin Kleppmann
What you’ll build: A horizontally scalable authorization service that multiple applications can call to make authorization decisions, with caching, consistency guarantees, and high availability.
Why it teaches authorization: In microservices architectures, authorization can’t live in each service. A centralized authorization service with proper caching and consistency is how companies like Airbnb (Himeji) and Carta (AuthZ) handle permissions at scale.
Core challenges you’ll face:
- Service API design → maps to authorization as a service
- Caching strategy → maps to performance at scale
- Cache invalidation → maps to consistency when permissions change
- High availability → maps to authorization without single points of failure
Key Concepts:
- Authorization Service Patterns: Airbnb’s Himeji paper (QCon 2020)
- Caching Strategies: “Designing Data-Intensive Applications” Chapter 5 - Kleppmann
- gRPC Service Design: “gRPC: Up and Running” Chapter 3 - Indrasiri
Difficulty: Expert Time estimate: 3-4 weeks Prerequisites: Projects 11 and 12, distributed systems basics
Real world outcome:
// Your distributed authorization service
// Server-side: AuthZ Service
authzService := NewAuthZService(Config{
PolicyEngine: opaEngine,
CacheType: "redis-cluster",
CacheTTL: 5 * time.Minute,
Replicas: 3,
})
// Client-side: SDK for applications
authzClient := authz.NewClient("authz.internal:443", authz.WithCaching(true))
// In your microservice
func (s *DocumentService) DeleteDocument(ctx context.Context, req *DeleteRequest) error {
// Single authorization call
decision, err := s.authz.Check(ctx, &authz.CheckRequest{
Subject: userFromContext(ctx),
Action: "delete",
Resource: fmt.Sprintf("document:%s", req.DocumentID),
Context: map[string]any{
"resource_type": "document",
"department": req.Department,
},
})
if err != nil {
return fmt.Errorf("authorization service unavailable: %w", err)
}
if !decision.Allowed {
return status.Error(codes.PermissionDenied, decision.Reason)
}
// Proceed with deletion
return s.repo.Delete(ctx, req.DocumentID)
}
// Batch check for UI (which buttons to show)
decisions, _ := s.authz.BatchCheck(ctx, &authz.BatchCheckRequest{
Subject: userFromContext(ctx),
Checks: []*authz.Check{
{Action: "edit", Resource: "document:123"},
{Action: "delete", Resource: "document:123"},
{Action: "share", Resource: "document:123"},
{Action: "view_history", Resource: "document:123"},
},
})
// Returns: {edit: true, delete: false, share: true, view_history: true}
// Cache invalidation when permissions change
authzService.InvalidateCache(InvalidateRequest{
Subject: "user:alice", // All of Alice's cached decisions
})
authzService.InvalidateCache(InvalidateRequest{
Resource: "document:123", // All cached decisions for this document
})
// Observe metrics
authzService.Metrics()
// Output:
// - requests_total: 1.2M/min
// - cache_hit_ratio: 94.5%
// - latency_p50: 1.2ms
// - latency_p99: 15ms
Implementation Hints:
Service architecture:
┌─────────────────────────────────────────────────────────────────────────┐
│ Load Balancer │
└─────────────────────────────────────────────────────────────────────────┘
│
┌──────────────────────────┼──────────────────────────┐
│ │ │
┌────▼────┐ ┌────▼────┐ ┌────▼────┐
│ AuthZ │ │ AuthZ │ │ AuthZ │
│ Node 1 │ │ Node 2 │ │ Node 3 │
└────┬────┘ └────┬────┘ └────┬────┘
│ │ │
│ ┌───────────────────────┼───────────────────────┐ │
│ │ │ │ │
▼ ▼ ▼ ▼ ▼
┌─────────────────────────────────────────────────────────────┐
│ Redis Cluster (Cache) │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ PostgreSQL (Persistent) │
│ - Policies │
│ - Relationships │
│ - Audit Log │
└─────────────────────────────────────────────────────────────┘
Cache key design:
// Hierarchical cache keys for efficient invalidation
cacheKey := fmt.Sprintf("authz:v1:%s:%s:%s",
sha256(subject)[:8],
sha256(action)[:8],
sha256(resource)[:8],
)
// Cache tags for invalidation
tags := []string{
fmt.Sprintf("subject:%s", subject),
fmt.Sprintf("resource:%s", resource),
fmt.Sprintf("resource_type:%s", resourceType),
}
Consistency strategies:
- Short TTL: Cache expires quickly, eventual consistency
- Event-driven: Publish permission changes, subscribers invalidate
- Zookie tokens: Zanzibar-style consistency tokens
Consider:
- What happens if the authz service is down?
- How do you handle the thundering herd when cache expires?
- How do you version policy changes?
Learning milestones:
- Service handles basic checks → You understand authz as a service
- Caching improves latency → You understand cache design
- Invalidation works correctly → You understand consistency
- Service handles failure gracefully → You understand availability
Project 14: Authorization Audit System
- File: LEARN_AUTHORIZATION_DEEP_DIVE.md
- Main Programming Language: Go
- Alternative Programming Languages: Python, Rust
- Coolness Level: Level 3: Genuinely Clever
- Business Potential: 3. The “Service & Support” Model
- Difficulty: Level 3: Advanced
- Knowledge Area: Security Auditing / Compliance
- Software or Tool: AuthZ Audit Engine
- Main Book: “Security in Computing” by Pfleeger
What you’ll build: A comprehensive audit system that logs every authorization decision, enables compliance queries (who accessed what, when), detects anomalies, and generates reports for security teams.
Why it teaches authorization: Authorization isn’t just about making decisions—it’s about proving you made the right decisions. Audit logs are required for compliance (SOC 2, HIPAA, GDPR) and essential for security investigations.
Core challenges you’ll face:
- Structured audit logging → maps to capturing complete context
- Efficient storage and retrieval → maps to time-series data at scale
- Compliance queries → maps to answering “who accessed what when”
- Anomaly detection → maps to identifying unusual access patterns
Key Concepts:
- Security Auditing: “Security in Computing” Chapter 9 - Pfleeger
- Time-Series Databases: “Designing Data-Intensive Applications” Chapter 3 - Kleppmann
- Log Analysis: “The Practice of Network Security Monitoring” Chapter 8 - Bejtlich
Difficulty: Advanced Time estimate: 2-3 weeks Prerequisites: Project 13, understanding of logging systems
Real world outcome:
// Your audit system
audit := NewAuthZAudit(Config{
Storage: "clickhouse", // Time-series optimized
Retention: 365 * 24 * time.Hour,
AnomalyDetection: true,
})
// Integrate with authorization service
authzService.OnDecision(func(decision Decision) {
audit.Log(AuditEntry{
Timestamp: time.Now(),
Subject: decision.Subject,
Action: decision.Action,
Resource: decision.Resource,
Decision: decision.Allowed,
Reason: decision.Reason,
PolicyID: decision.PolicyID,
SourceIP: decision.Context["source_ip"],
UserAgent: decision.Context["user_agent"],
RequestID: decision.Context["request_id"],
})
})
// Compliance queries
// "Who accessed patient records in the last 30 days?"
results, _ := audit.Query(Query{
TimeRange: TimeRange{Start: now.AddDate(0, 0, -30), End: now},
Filter: And(
Eq("resource_type", "patient_record"),
Eq("decision", true),
),
GroupBy: []string{"subject"},
})
// "What did user X access yesterday?"
results, _ := audit.Query(Query{
TimeRange: TimeRange{Start: yesterday, End: today},
Filter: Eq("subject", "user:alice"),
OrderBy: "timestamp DESC",
})
// "Failed access attempts to sensitive resources"
results, _ := audit.Query(Query{
TimeRange: TimeRange{Start: lastWeek, End: now},
Filter: And(
Eq("decision", false),
In("resource_type", []string{"financial", "pii", "secret"}),
),
})
// Anomaly detection
anomalies := audit.DetectAnomalies(AnomalyConfig{
BaselinePeriod: 30 * 24 * time.Hour,
DetectionWindow: 24 * time.Hour,
Sensitivity: "medium",
})
// Output:
// - user:bob accessed 500 documents (normal: 10-20)
// - service:reporting accessed PII at 3am (normal hours: 9am-6pm)
// - user:alice denied access to admin resources 50 times in 1 hour
// Generate compliance report
report := audit.GenerateReport(ReportConfig{
Type: "SOC2",
Period: Quarter,
Sections: []string{
"access_summary",
"privileged_access",
"failed_attempts",
"policy_changes",
},
})
Implementation Hints:
Audit entry schema:
type AuditEntry struct {
ID string `json:"id"`
Timestamp time.Time `json:"timestamp"`
Subject string `json:"subject"` // user:alice, service:api
SubjectType string `json:"subject_type"` // user, service, api_key
Action string `json:"action"` // read, write, delete
Resource string `json:"resource"` // document:123
ResourceType string `json:"resource_type"` // document, folder
Decision bool `json:"decision"` // true=allowed, false=denied
Reason string `json:"reason"` // policy that matched
PolicyID string `json:"policy_id"` // authz-policy-123
Context map[string]string `json:"context"` // additional metadata
// Request context
SourceIP string `json:"source_ip"`
UserAgent string `json:"user_agent"`
RequestID string `json:"request_id"`
SessionID string `json:"session_id"`
// For traceability
ServiceName string `json:"service_name"` // which service made the check
TraceID string `json:"trace_id"` // distributed tracing
}
Storage considerations:
- Use a columnar store (ClickHouse, TimescaleDB) for efficient aggregation
- Partition by time for efficient range queries
- Index on subject, resource, decision for common queries
- Compress old data to reduce storage costs
Anomaly detection approaches:
- Baseline comparison: Compare current behavior to historical average
- Peer comparison: Compare user to similar users
- Time-based: Flag access outside normal hours
- Velocity: Flag unusual number of requests
Consider:
- How do you handle audit log tampering?
- How do you ensure audit logging doesn’t slow down authorization?
- How do you handle GDPR “right to be forgotten” for audit logs?
Learning milestones:
- All decisions are logged → You understand audit completeness
- Queries return correct results → You understand audit retrieval
- Anomalies are detected → You understand pattern analysis
- Reports are generated → You understand compliance requirements
Project 15: Authorization Testing Framework
- File: LEARN_AUTHORIZATION_DEEP_DIVE.md
- Main Programming Language: Go
- Alternative Programming Languages: Python, TypeScript
- Coolness Level: Level 3: Genuinely Clever
- Business Potential: 3. The “Service & Support” Model
- Difficulty: Level 2: Intermediate
- Knowledge Area: Security Testing
- Software or Tool: AuthZ Test Framework
- Main Book: “The Art of Software Security Assessment” by Dowd, McDonald, Schuh
What you’ll build: A testing framework specifically designed for authorization systems—defining test scenarios, asserting permission behavior, detecting policy regressions, and verifying that sensitive resources are protected.
Why it teaches authorization: Authorization bugs are security bugs. A testing framework helps you verify that your permissions work as expected, catch regressions when policies change, and ensure you haven’t accidentally opened access to sensitive resources.
Core challenges you’ll face:
- Test scenario DSL → maps to expressing authorization test cases
- Fixture management → maps to setting up users, roles, and resources
- Regression detection → maps to catching unintended permission changes
- Coverage analysis → maps to ensuring all policies are tested
Key Concepts:
- Security Testing: “The Art of Software Security Assessment” Chapter 4 - Dowd et al.
- Property-Based Testing: “Property-Based Testing with PropEr, Erlang, and Elixir” Chapter 1
- Access Control Testing: OWASP Testing Guide - Authorization Testing
Difficulty: Intermediate Time estimate: 1-2 weeks Prerequisites: Project 2, understanding of testing frameworks
Real world outcome:
// Your authorization testing framework
suite := authztest.NewSuite(authzService)
// Define test fixtures
suite.Fixtures(func(f *authztest.Fixtures) {
f.User("admin-alice", authztest.WithRoles("admin"))
f.User("editor-bob", authztest.WithRoles("editor"))
f.User("viewer-charlie", authztest.WithRoles("viewer"))
f.User("guest-dave") // No roles
f.Resource("doc:public", authztest.Attrs{"visibility": "public"})
f.Resource("doc:internal", authztest.Attrs{"visibility": "internal"})
f.Resource("doc:secret", authztest.Attrs{"visibility": "secret"})
})
// Define test scenarios
suite.Scenario("Basic RBAC", func(s *authztest.Scenario) {
s.Test("admin can do everything", func(t *authztest.T) {
t.Assert("admin-alice").Can("read", "doc:public")
t.Assert("admin-alice").Can("write", "doc:public")
t.Assert("admin-alice").Can("delete", "doc:public")
t.Assert("admin-alice").Can("read", "doc:secret")
})
s.Test("viewer can only read", func(t *authztest.T) {
t.Assert("viewer-charlie").Can("read", "doc:public")
t.Assert("viewer-charlie").Cannot("write", "doc:public")
t.Assert("viewer-charlie").Cannot("delete", "doc:public")
})
s.Test("guest cannot access anything", func(t *authztest.T) {
t.Assert("guest-dave").CannotAny("read", "write", "delete", "doc:internal")
})
})
suite.Scenario("Sensitive Resources", func(s *authztest.Scenario) {
// These resources should NEVER be accessible to non-admins
s.Test("secrets are protected", func(t *authztest.T) {
t.AssertNobodyExcept("admin-alice").Can("read", "doc:secret")
t.AssertNobodyExcept("admin-alice").Can("write", "doc:secret")
})
})
// Property-based testing
suite.Property("no privilege escalation", func(p *authztest.Property) {
p.ForAll(func(user authztest.User, resource authztest.Resource) bool {
// If user can write, they should be able to read
if p.Can(user, "write", resource) {
return p.Can(user, "read", resource)
}
return true
})
})
// Run tests
results := suite.Run()
// Output:
// ✓ Basic RBAC: admin can do everything (4 assertions)
// ✓ Basic RBAC: viewer can only read (3 assertions)
// ✓ Basic RBAC: guest cannot access anything (3 assertions)
// ✓ Sensitive Resources: secrets are protected (2 assertions)
// ✓ Property: no privilege escalation (1000 random checks)
//
// Coverage:
// - Policies tested: 15/18 (83%)
// - Roles tested: 4/4 (100%)
// - Resource types tested: 3/5 (60%)
// Regression detection
suite.CompareToBaseline("baseline.json")
// Output:
// ⚠️ REGRESSION DETECTED:
// - viewer-charlie can now write doc:internal (was: denied)
// - Policy "editor-write" was modified
Implementation Hints:
Test framework architecture:
type Suite struct {
authz AuthZService
fixtures *Fixtures
scenarios []*Scenario
}
type Assertion struct {
Subject string
Action string
Resource string
Expected bool
Actual bool
Duration time.Duration
}
type T struct {
suite *Suite
assertions []Assertion
}
func (t *T) Assert(subject string) *AssertionBuilder {
return &AssertionBuilder{t: t, subject: subject}
}
func (ab *AssertionBuilder) Can(action, resource string) {
result := ab.t.suite.authz.Check(ab.subject, action, resource)
ab.t.assertions = append(ab.t.assertions, Assertion{
Subject: ab.subject,
Action: action,
Resource: resource,
Expected: true,
Actual: result.Allowed,
})
}
Key features to implement:
- Fluent API:
t.Assert("alice").Can("read", "doc:1") - Negative assertions:
t.Assert("bob").Cannot("delete", "doc:1") - Bulk assertions:
t.AssertNobodyExcept(...).Can(...) - Property-based: Random users/resources to find edge cases
- Baseline comparison: Detect regressions from last known good state
- Coverage tracking: Which policies/roles/resources are tested
Consider:
- How do you generate realistic random users and resources?
- How do you handle flaky tests from eventual consistency?
- How do you test time-based or context-dependent policies?
Learning milestones:
- Basic assertions work → You understand auth testing fundamentals
- Property-based tests find bugs → You understand generative testing
- Regressions are detected → You understand baseline comparison
- Coverage is tracked → You understand test completeness
Project Comparison Table
| Project | Difficulty | Time | Depth of Understanding | Fun Factor | Business Value |
|---|---|---|---|---|---|
| 1. Permission Checker | Beginner | Weekend | ⭐⭐ | ⭐⭐ | Resume Gold |
| 2. Basic RBAC | Beginner | Weekend | ⭐⭐⭐ | ⭐⭐ | Micro-SaaS |
| 3. ACL Implementation | Intermediate | 1 week | ⭐⭐⭐ | ⭐⭐⭐ | Resume Gold |
| 4. Unix Permissions | Intermediate | 1-2 weeks | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ | Resume Gold |
| 5. Claims-Based Auth | Intermediate | 1 week | ⭐⭐⭐ | ⭐⭐⭐ | Micro-SaaS |
| 6. Hierarchical Perms | Intermediate | 1-2 weeks | ⭐⭐⭐⭐ | ⭐⭐⭐ | Service & Support |
| 7. Multi-Tenant AuthZ | Advanced | 2-3 weeks | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | Open Core |
| 8. ABAC System | Advanced | 2-3 weeks | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | Open Core |
| 9. Row-Level Security | Advanced | 2-3 weeks | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ | Open Core |
| 10. OAuth 2.0 Scopes | Intermediate | 1-2 weeks | ⭐⭐⭐ | ⭐⭐⭐ | Service & Support |
| 11. Policy Engine (OPA) | Expert | 1 month+ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | Industry Disruptor |
| 12. Zanzibar Clone | Master | 1-2 months | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | Industry Disruptor |
| 13. Distributed AuthZ | Expert | 3-4 weeks | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | Open Core |
| 14. Audit System | Advanced | 2-3 weeks | ⭐⭐⭐⭐ | ⭐⭐⭐ | Service & Support |
| 15. Testing Framework | Intermediate | 1-2 weeks | ⭐⭐⭐ | ⭐⭐⭐ | Service & Support |
Recommended Learning Path
For Beginners (New to Authorization)
Start with: Project 1 → Project 2 → Project 5
This path teaches you:
- The fundamental permission check
- How roles abstract permissions (most common model)
- How tokens carry authorization data (modern web apps)
Time: 2-3 weeks
For Intermediate Developers (Know RBAC Basics)
Start with: Project 4 → Project 6 → Project 8
This path teaches you:
- Deep understanding of classic permission models
- How permissions flow through hierarchies
- The most flexible authorization model (ABAC)
Time: 4-6 weeks
For Advanced Engineers (Want to Build AuthZ Infrastructure)
Start with: Project 11 → Project 12 → Project 13
This path teaches you:
- How to build a policy engine like OPA
- How Google does authorization at scale
- How to build production authorization services
Time: 2-3 months
For Security-Focused Engineers
Start with: Project 9 → Project 14 → Project 15
This path teaches you:
- Authorization at the data layer
- Audit and compliance requirements
- Testing authorization systems
Time: 5-7 weeks
Final Capstone Project: Full Authorization Platform
- File: LEARN_AUTHORIZATION_DEEP_DIVE.md
- Main Programming Language: Go
- Alternative Programming Languages: Rust
- Coolness Level: Level 5: Pure Magic (Super Cool)
- Business Potential: 5. The “Industry Disruptor”
- Difficulty: Level 5: Master
- Knowledge Area: Authorization Infrastructure
- Software or Tool: Complete AuthZ Platform
- Main Book: “Zanzibar Paper” + “Designing Data-Intensive Applications”
What you’ll build: A complete authorization platform combining everything you’ve learned—relationship-based access control (Zanzibar-style), a policy engine for custom rules, multi-tenant support, distributed service architecture, comprehensive audit logging, and a testing framework.
Why it teaches authorization: This is the real deal. Building a complete authorization platform that could actually be used in production teaches you how all the pieces fit together and the trade-offs involved in real authorization systems.
Core challenges you’ll face:
- Unified model → combining ReBAC + ABAC elegantly
- API design → developer experience for authorization
- Performance → sub-10ms latency at scale
- Operations → deployment, monitoring, debugging
Components to integrate:
- Relationship storage (from Project 12)
- Policy engine (from Project 11)
- Multi-tenancy (from Project 7)
- Distributed service (from Project 13)
- Audit system (from Project 14)
- Testing framework (from Project 15)
Real world outcome:
// Your authorization platform
platform := authzplatform.New(Config{
Storage: "cockroachdb",
Cache: "redis-cluster",
PolicyEngine: "builtin",
})
// Schema + Policies
platform.Configure(`
# Relationship-based
definition organization {
relation admin: user
relation member: user
permission manage = admin
permission view = member + admin
}
definition document {
relation org: organization
relation owner: user
relation viewer: user
permission edit = owner + org->admin
permission view = viewer + owner + org->member
permission delete = owner
}
# Policy-based extensions
policy overtime_approval {
target: action == "approve" and resource.type == "overtime"
condition:
subject.role == "manager" and
resource.amount <= 1000 and
environment.time.hour >= 9 and environment.time.hour <= 18
effect: permit
}
`)
// SDK usage in applications
client := platform.Client()
// Check with tracing
decision := client.Check(ctx, authz.Request{
Subject: "user:alice",
Action: "edit",
Resource: "document:design-doc",
Context: map[string]any{"org": "acme-corp"},
})
// Includes: allowed, reason, trace of evaluation, latency
// Bulk check for UI
buttons := client.BatchCheck(ctx, authz.BatchRequest{
Subject: "user:alice",
Checks: []Check{
{Action: "edit", Resource: "document:1"},
{Action: "delete", Resource: "document:1"},
// ... more
},
})
// Admin dashboard shows:
// - Real-time request rates
// - Cache hit ratios
// - P99 latencies
// - Policy evaluation breakdown
// - Audit log stream
// - Anomaly alerts
Time estimate: 2-3 months Prerequisites: Complete at least 10 of the earlier projects
Learning milestones:
- Platform handles basic checks → You’ve integrated the core components
- ReBAC + ABAC work together → You understand hybrid models
- Platform handles 10K+ RPS → You understand performance optimization
- Audit and monitoring work → You understand operational requirements
- Testing catches regressions → You understand authorization quality
Summary
| # | Project | Main Language |
|---|---|---|
| 1 | Simple Permission Checker | Python |
| 2 | Basic RBAC System | Python |
| 3 | Access Control List (ACL) Implementation | Go |
| 4 | Unix-Style File Permissions | C |
| 5 | Claims-Based Authorization | TypeScript |
| 6 | Hierarchical Permission System | Python |
| 7 | Multi-Tenant Authorization System | Go |
| 8 | Attribute-Based Access Control (ABAC) System | Go |
| 9 | Row-Level Security Engine | Go |
| 10 | OAuth 2.0 Scope-Based Authorization | Go |
| 11 | Policy Engine (OPA-Style) | Go |
| 12 | Zanzibar-Style Relationship-Based Access Control | Go |
| 13 | Distributed Authorization Service | Go |
| 14 | Authorization Audit System | Go |
| 15 | Authorization Testing Framework | Go |
| Capstone | Full Authorization Platform | Go |
Additional Resources
Papers
- “Zanzibar: Google’s Consistent, Global Authorization System” (USENIX ATC 2019)
- “RBAC96: Role-Based Access Control Models” (Sandhu et al., 1996)
- “ABAC: Attribute-Based Access Control” (NIST SP 800-162)
Open Source Projects to Study
- SpiceDB: Open source Zanzibar implementation
- Open Policy Agent (OPA): Policy engine with Rego language
- Casbin: Authorization library supporting multiple models
- Ory Keto: Zanzibar-inspired access control
- Cerbos: Policy-as-code authorization
Books
- “Role-Based Access Control” by Ferraiolo, Kuhn, Chandramouli
- “OAuth 2 in Action” by Justin Richer and Antonio Sanso
- “API Security in Action” by Neil Madden
- “Designing Data-Intensive Applications” by Martin Kleppmann
- “Security Engineering” by Ross Anderson
Authorization is the art of saying “no” in the right way. Master it, and you’ve mastered a critical aspect of software security.