← Back to all projects

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:

  1. Subject: Who is requesting access? (user, service, API key)
  2. Action: What do they want to do? (read, write, delete, approve)
  3. 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

  1. Discretionary Access Control (DAC): Resource owners decide who can access
    • Example: Unix file permissions, Google Drive sharing
  2. Mandatory Access Control (MAC): System-enforced policies, users can’t override
    • Example: Military classification levels (TOP SECRET > SECRET > CONFIDENTIAL)
  3. Role-Based Access Control (RBAC): Permissions assigned to roles, roles assigned to users
    • Example: Admin, Editor, Viewer roles in a CMS
  4. 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”
  5. 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:

  1. Check for explicit deny (with wildcard expansion) → if found, return False
  2. Check for explicit grant (with wildcard expansion) → if found, return True
  3. Default deny (closed policy) or default allow (open policy)

Wildcard matching:

  • * matches any single segment
  • ** matches any number of segments
  • Example: document:* matches document:123 but not document:folder:123

Consider these questions:

  • What happens if both grant(alice, read, *) and deny(alice, read, secret) exist?
  • How do you handle the order of evaluation?
  • What’s the most efficient data structure for wildcard matching?

Learning milestones:

  1. Basic grant/deny works → You understand the permission triple
  2. Wildcards work correctly → You understand pattern matching in permissions
  3. Deny always wins → You understand conflict resolution
  4. 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:

  1. Users can be assigned roles → You understand identity binding
  2. Roles aggregate permissions → You understand the indirection layer
  3. Multi-role users work → You understand permission union
  4. 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:

  1. Per-resource ACLs work → You understand object-centric permissions
  2. ACL inheritance works → You understand permission propagation
  3. Group expansion works → You understand principal abstraction
  4. 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:

  1. Basic rwx works for owner/group/other → You understand permission classes
  2. Directory permissions work correctly → You understand context-dependent auth
  3. Setuid changes effective uid → You understand privilege escalation
  4. 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:

  1. Extract claims from JWT → You understand token structure
  2. Simple claim checks work → You understand basic policy evaluation
  3. Complex boolean policies work → You understand policy composition
  4. 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:

  1. Role inheritance works → You understand transitive roles
  2. Resource inheritance works → You understand permission propagation
  3. Combined inheritance works → You understand complex permission resolution
  4. 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:

  1. Tenant isolation is bulletproof → You understand SaaS security fundamentals
  2. Tenant hierarchies work → You understand enterprise organization structures
  3. Cross-tenant sharing works safely → You understand controlled boundary crossing
  4. 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:

  1. Simple attribute comparisons work → You understand attribute evaluation
  2. Complex conditions work → You understand boolean composition
  3. Multiple policies combine correctly → You understand policy resolution
  4. 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:

  1. Simple SELECT rewriting works → You understand query transformation
  2. Multiple tables with RLS work → You understand join authorization
  3. INSERT/UPDATE checking works → You understand write authorization
  4. 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:

  1. Scopes are issued in tokens → You understand scope granting
  2. Scopes are enforced at endpoints → You understand scope checking
  3. Client scope restrictions work → You understand scope limiting
  4. 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:

  1. Design a minimal policy language (rules, conditions, variables)
  2. Write a recursive descent parser
  3. Build an AST representation
  4. Write a tree-walking interpreter
  5. Add policy testing capabilities
  6. 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:

  1. Parser produces valid AST → You understand language processing
  2. Simple policies evaluate correctly → You understand rule evaluation
  3. Complex policies with variables work → You understand unification
  4. 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:

  1. Direct relationships work → You understand tuple storage
  2. Computed permissions work → You understand permission expressions
  3. Arrow expressions traverse graph → You understand recursive authorization
  4. 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:

  1. Short TTL: Cache expires quickly, eventual consistency
  2. Event-driven: Publish permission changes, subscribers invalidate
  3. 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:

  1. Service handles basic checks → You understand authz as a service
  2. Caching improves latency → You understand cache design
  3. Invalidation works correctly → You understand consistency
  4. 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:

  1. Baseline comparison: Compare current behavior to historical average
  2. Peer comparison: Compare user to similar users
  3. Time-based: Flag access outside normal hours
  4. 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:

  1. All decisions are logged → You understand audit completeness
  2. Queries return correct results → You understand audit retrieval
  3. Anomalies are detected → You understand pattern analysis
  4. 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:

  1. Fluent API: t.Assert("alice").Can("read", "doc:1")
  2. Negative assertions: t.Assert("bob").Cannot("delete", "doc:1")
  3. Bulk assertions: t.AssertNobodyExcept(...).Can(...)
  4. Property-based: Random users/resources to find edge cases
  5. Baseline comparison: Detect regressions from last known good state
  6. 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:

  1. Basic assertions work → You understand auth testing fundamentals
  2. Property-based tests find bugs → You understand generative testing
  3. Regressions are detected → You understand baseline comparison
  4. 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

For Beginners (New to Authorization)

Start with: Project 1 → Project 2 → Project 5

This path teaches you:

  1. The fundamental permission check
  2. How roles abstract permissions (most common model)
  3. 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:

  1. Deep understanding of classic permission models
  2. How permissions flow through hierarchies
  3. 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:

  1. How to build a policy engine like OPA
  2. How Google does authorization at scale
  3. 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:

  1. Authorization at the data layer
  2. Audit and compliance requirements
  3. 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:

  1. Relationship storage (from Project 12)
  2. Policy engine (from Project 11)
  3. Multi-tenancy (from Project 7)
  4. Distributed service (from Project 13)
  5. Audit system (from Project 14)
  6. 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:

  1. Platform handles basic checks → You’ve integrated the core components
  2. ReBAC + ABAC work together → You understand hybrid models
  3. Platform handles 10K+ RPS → You understand performance optimization
  4. Audit and monitoring work → You understand operational requirements
  5. 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.