docs/.NET Pbac Abac Rbac/pbac
Edit on GitHub

PBAC — Policy-Based Access Control

One-Paragraph Summary

PBAC organizes authorization decisions into named policies — composable units that combine role checks, claim checks, attribute checks, and business rules. In .NET, PBAC is the native authorization model: AddAuthorization(options => options.AddPolicy("CanApprove", ...)). A policy is not a new access control theory — it's an organizational pattern that wraps RBAC and ABAC into maintainable, reusable, testable units. PBAC is how you make complex authorization manageable in real systems.

How It Works

Why PBAC Is the Practical Choice in .NET

ASP.NET Core's authorization system IS policy-based. Even [Authorize(Roles = "Admin")] is syntactic sugar for a policy with a role requirement. When you write [Authorize(Policy = "CanApproveDocument")], you're using PBAC.

PBAC is not a replacement for RBAC or ABAC — it's the container that holds them.

text
PBAC Policy = RBAC Requirements + ABAC Requirements + Business Rules

In This Project

Policy Registration (composing RBAC + ABAC)

csharp
// Simple RBAC-only policy
.AddPolicy(AppPolicies.RequireAdmin, policy =>
    policy.RequireRole(AppRoles.Admin))

// PBAC: combines claim check (ABAC) with authentication
.AddPolicy(AppPolicies.CanCreateDocument, policy =>
    policy.RequireAuthenticatedUser()
        .RequireClaim(AppClaims.AccountStatus, "Active"))

// PBAC: composite requirement with custom handler
.AddPolicy(AppPolicies.CanApproveDocument, policy =>
    policy.AddRequirements(new DocumentApprovalRequirement()))

Composite Handler (PBAC wrapping RBAC + ABAC)

csharp
public class DocumentApprovalHandler : AuthorizationHandler<DocumentApprovalRequirement, Document>
{
    protected override Task HandleRequirementAsync(...)
    {
        // Step 1: RBAC — must be Manager or Admin
        if (!user.IsInRole("Manager") && !user.IsInRole("Admin"))
            return Task.CompletedTask;

        // Step 2: Business rule — document must be pending
        if (resource.Status != DocumentStatus.PendingApproval)
            return Task.CompletedTask;

        // Step 3: ABAC — department must match
        if (userDepartment != resource.Department)
            return Task.CompletedTask;

        context.Succeed(requirement);
        return Task.CompletedTask;
    }
}

Policies Defined in This Project

PolicyTypeRequirements
RequireAdminRBAC-onlyRole = Admin
RequireManagerRBAC-onlyRole = Manager, RegionalManager, or Admin
RequireAuditorRBAC-onlyRole = Auditor or Admin
CanCreateDocumentPBACAuthenticated + AccountStatus = Active
CanApproveDocumentPBAC + ABACManager role + same dept + pending status
CanViewSensitiveDocumentPBAC + ABACSame tenant + clearance >= sensitivity
CanViewAuditTrailPBACAuditor/Admin role + active account
CanManageTenantUsersPBACAdmin/TenantAdmin + active account
ServiceReadDocumentsScope-basedHas documents:read scope (machine client)

Strengths

  • Composable: Combine any number of requirements into one policy
  • Maintainable: Policy names are self-documenting ("CanApproveDocument")
  • Testable: Each handler is independently unit-testable
  • Centralized: All policies defined in one place (AddAuthorization)
  • Native to .NET: This is how ASP.NET Core authorization works
  • Reusable: Same policy used in multiple endpoints

Weaknesses

  • Indirection: Developers must look up what "CanApproveDocument" actually checks
  • Static registration: Default policies are defined at startup (dynamic policies need custom IAuthorizationPolicyProvider)
  • Handler complexity: Composite handlers can grow complex
  • Not a standard: Unlike XACML/ABAC, there's no formal PBAC specification

When PBAC Is Enough

  • Most .NET applications (it's the default model)
  • When you need readable, named access control rules
  • When combining RBAC and ABAC checks
  • When you want centralized policy management
  • When policies can be defined at compile time

When PBAC Becomes Insufficient

  • Hundreds of dynamic policies managed by business users → need a policy engine
  • Real-time policy updates without redeployment → need IAuthorizationPolicyProvider + database
  • Cross-system policy evaluation → need a centralized policy decision point (PDP)

How PBAC Wraps RBAC and ABAC

Declarative vs Imperative Authorization

StyleWhen to UseExample
DeclarativeNo resource needed at decision time[Authorize(Policy = "RequireAdmin")]
ImperativeResource must be loaded firstauthService.AuthorizeAsync(user, doc, "CanApprove")
csharp
// Declarative: policy evaluated before endpoint runs
app.MapGet("/admin/policies", ...)
    .RequireAuthorization(AppPolicies.RequireAdmin);

// Imperative: resource loaded, then policy evaluated
var doc = await repo.GetByIdAsync(id);
var result = await authService.AuthorizeAsync(User, doc, AppPolicies.CanApproveDocument);
if (!result.Succeeded) return Forbid();

Common Mistakes

  1. Not using policies: Scattering IsInRole and HasClaim checks across controllers instead of centralizing in policies
  2. Monolithic handlers: One handler that checks 15 things. Split into multiple requirements
  3. Calling context.Fail(): Usually wrong — not calling Succeed() is a soft fail. Fail() blocks all other handlers
  4. Forgetting resource-based auth: Using only declarative policies when you need to check the resource
  5. Policy name soup: Poor naming like "Policy1", "Policy2". Names should be verbs: "CanApprove", "CanView"

Memory Hook

PBAC is a recipe book. Each recipe (policy) lists ingredients (requirements). Some ingredients are role-based (need chef certification), some are attribute-based (need fresh ingredients from this supplier). The recipe combines them into one instruction.

Tradeoff Table

AspectRatingNotes
Simplicity★★★★☆Named policies are readable
Granularity★★★★★As granular as the requirements inside
Scalability★★★★☆Scales with policy count
Auditability★★★★☆Policy names are self-documenting
Flexibility★★★★☆Composable, but static by default
Performance★★★★★Minimal overhead over raw checks

Cheat Sheet

text
PBAC = Named Policy → [Requirement₁, Requirement₂, ...] → Handlers → Decision

.NET: AddPolicy("Name", policy => policy.RequireRole(...).RequireClaim(...).AddRequirements(...))
Declarative: [Authorize(Policy = "Name")]
Imperative: IAuthorizationService.AuthorizeAsync(user, resource, "Name")

PBAC wraps RBAC and ABAC — it's the organizational layer, not a replacement.

Start with RBAC-only policies → add claim checks → add custom handlers as needed.

Staff-Level Interview Questions

  1. "How is PBAC different from RBAC and ABAC? Is it a separate model or an organizational pattern?"
  2. "When would you need a custom IAuthorizationPolicyProvider? What problem does it solve?"
  3. "Walk through the lifecycle of a policy evaluation in ASP.NET Core, from attribute to decision."
  4. "How would you design policies for a system with 200+ authorization rules? How do you keep them maintainable?"
  5. "What's the difference between not calling Succeed() and calling Fail() in an authorization handler?"
  • RBAC → docs/rbac.md
  • ABAC → docs/abac.md
  • Comparison → docs/rbac-vs-abac-vs-pbac.md
  • Custom handlers → docs/custom-authorization-handlers.md
  • .NET implementation → docs/dotnet-authorization-implementation.md