ASP.NET Core Authorization
When to Use This Skill
Use this skill when:
- Implementing policy-based authorization in ASP.NET Core
- Protecting API endpoints with scope-based or claim-based checks
- Writing custom
IAuthorizationHandlerimplementations - Configuring authorization for Minimal APIs, controllers, or Razor Pages
- Enforcing role-based access control using OIDC claims
- Combining multiple authorization requirements into composite policies
Core Principles
- Policy-Based Over Role-Based — Use authorization policies instead of
[Authorize(Roles = "...")]. Policies are composable, testable, and decoupled from claim types. - Scope ≠ Permission — OAuth scopes represent what the client is allowed to do. User claims represent what the user is allowed to do. Combine both for proper API authorization.
- Authorization is Separate from Authentication — Authentication (see
aspnetcore-authentication) establishes identity. Authorization decides access based on that identity. - Fail Closed — Default to denying access. Require explicit authorization on all endpoints.
- Resource-Based When Needed — For decisions that depend on the resource being accessed (e.g., "can this user edit this document?"), use
IAuthorizationServicewith resource-based authorization.
Related Skills
aspnetcore-authentication— Authentication middleware that provides the identityclaims-authorization— Advanced claims transformation and authorization patternsidentityserver-configuration— Server-side scope and resource configurationoauth-oidc-protocols— Understanding scopes, claims, and token contents
Docs: https://docs.duendesoftware.com/identityserver/apis/aspnetcore/authorization/
Pattern 1: Basic Policy-Based Authorization
Define policies at startup and reference them on endpoints:
Applying Policies
Pattern 2: Scope-Based Authorization for APIs
APIs protected by IdentityServer need to validate scopes from the access token. Scopes represent what the client application is permitted to do.
Simple Scope Check
Scope as Space-Delimited String
When EmitScopesAsSpaceDelimitedStringInJwt = true on IdentityServer, scopes arrive as a single space-delimited string rather than an array. Use a custom handler:
Pattern 3: Custom Authorization Handlers
For complex authorization logic, implement IAuthorizationHandler:
Multiple Handlers for One Requirement
When any handler succeeding should grant access (OR logic):
Key concept: Multiple requirements in a policy use AND logic (all must be satisfied). Multiple handlers for the same requirement use OR logic (any can satisfy it).
Pattern 4: Resource-Based Authorization
When authorization depends on the resource being accessed, use IAuthorizationService:
Using in a Controller
Pattern 5: Minimal API Authorization
Minimal APIs use the same authorization system with a fluent API:
Pattern 6: Combining Client Scope + User Claims
In APIs protected by IdentityServer, proper authorization often requires checking both the client's scope and the user's claims:
Why both? A malicious client could request broad scopes, but the user may not have permission. A privileged user operating through a restricted client should be limited by that client's scopes.
Pattern 7: Fallback and Default Policies
Tip: Set
FallbackPolicyto require authentication, then use[AllowAnonymous]only on endpoints that genuinely need it (health checks, public assets).
Common Pitfalls
1. Using Role Strings Instead of Policies
2. Not Registering Authorization Handlers
3. Calling context.Fail() in Handlers
context.Fail() actively denies authorization regardless of what other handlers say — it's a hard veto. Not calling context.Succeed() simply means "I have no opinion"; other handlers can still satisfy the requirement.
Only call
context.Fail()when you need to guarantee denial even if other handlers would approve (e.g., a security blocklist check). In most cases, simply omit theSucceed()call.


