Building Multiple Requirements and Auth Policy Handlers in ASP NET
Learn how to build flexible authorization policies in ASP.NET Core with multiple requirements and handlers per requirement. We'll explore real-world scenarios including subscription tiers, educational access, user suspension, and geographic restrictions.
When building APIs, authorization often gets more complex than "is the user logged in?"
Real-world applications need to handle multiple conditions and more complex rules for authorization.
ASP.NET Core's policy-based authorization framework gives us powerful building blocks to handle these scenarios in a clean, maintainable way.
In this post, let's explore how to:
- Add multiple handlers to a single requirement (OR logic)
- Add multiple requirements to a single policy (AND logic)
- Use
context.Succeed()andcontext.Fail()appropriately - Build flexible authorization that adapts to changing business rules
I'm using Amazon Cognito as the identity provider for this example — however, the same concepts work with any other identity provider.
Thanks to AWS for sponsoring this article in my Cognito Series.
The Starting Point: A Simple ASP NET Authorization Policy
Let's start with a weather API that provides detailed forecast data only to premium subscribers.
Here's the initial policy setup. The requirement is straightforward and the handler checks for a premium subscription claim.
builder.Services.AddAuthorization(options =>{options.AddPolicy("CanAccessDetailedWeatherData", policy =>{policy.RequireAuthenticatedUser();policy.Requirements.Add(new SubscriptionTierRequirement());});});public class SubscriptionTierRequirement : IAuthorizationRequirement {}public class PaidSubscriptionHandler : AuthorizationHandler<SubscriptionTierRequirement>{protected override Task HandleRequirementAsync(AuthorizationHandlerContext context,SubscriptionTierRequirement requirement){var subscriptionClaim = context.User.FindFirst("subscription");if (subscriptionClaim?.Value == "premium"){context.Succeed(requirement);}return Task.CompletedTask;}}
Don't forget to register the handler:
builder.Services.AddSingleton<IAuthorizationHandler, PaidSubscriptionHandler>();
Apply the policy to an endpoint. When a user with "subscription": "premium" in their access token makes a request, the handler calls context.Succeed() and grants access.
Customize Amazon Cognito Tokens with Pre Token Generation Lambda Triggers

Multiple ASP NET Policy Handlers for a Requirement (OR Logic)
Now the business team comes along with a new requirement: universities and research institutions should also get access to detailed weather data, even without a premium subscription.
We could modify the existing PaidSubscriptionHandler and add more if statements. But that quickly becomes messy and hard to maintain.
A better approach: create a separate handler for the same requirement.
Why Multiple Handlers?
Multiple handlers are perfect when you want OR logic — the requirement succeeds if any handler calls context.Succeed().
In our case: grant access if the user is a premium subscriber OR belongs to an educational/research department.
Let's create a new handler that checks for department membership:
public class EducationalInstitutionHandler : AuthorizationHandler<SubscriptionTierRequirement>{public string[] AllowedDepartments { get; } = ["Education", "Research"];protected override Task HandleRequirementAsync(AuthorizationHandlerContext context,SubscriptionTierRequirement requirement){var departmentClaim = context.User.FindFirst("cognito:groups");if (departmentClaim != null &&AllowedDepartments.Contains(departmentClaim.Value, StringComparer.OrdinalIgnoreCase)){context.Succeed(requirement);}return Task.CompletedTask;}}
This handler reads the cognito:groups claim from the token. Amazon Cognito automatically includes group membership in tokens, making it perfect for role-based or department-based checks.
Amazon Cognito Access Control: Role-Based and Policy-Based Authorization

How Multiple Authorization Policy Handlers Are Evaluated
When ASP NET evaluates the policy, both handlers run and the requirement succeeds if at least one calls context.Succeed() — providing built-in OR logic.
This separation of concerns keeps each handler focused on a single authorization path.
Using context.Fail() to Explicitly Deny Access
Now we have another problem. Mike is a premium user, but he's been abusing the API with excessive requests. We've temporarily suspended his account by adding him to a "Suspended" user group in Cognito.
How do we ensure Mike doesn't get access, even though he has a premium subscription?
This is where context.Fail() comes in.
Create a handler that explicitly denies access for suspended users:
public class SuspendedUserHandler : AuthorizationHandler<SubscriptionTierRequirement>{protected override Task HandleRequirementAsync(AuthorizationHandlerContext context,SubscriptionTierRequirement requirement){if (context.User.IsInRole("Suspended")){context.Fail();}return Task.CompletedTask;}}
The key difference between not calling context.Succeed() and calling context.Fail():
- Not calling
context.Succeed(): The handler doesn't grant access, but other handlers might - Calling
context.Fail(): Explicitly denies access — even if other handlers already calledcontext.Succeed()
When context.Fail() is called, the entire requirement fails, regardless of what other handlers do.
Even though Mike is a premium user, the explicit context.Fail() overrides the success from the subscription handler.
Mike will continue to be blocked until he's removed from the Suspended group.
Multiple ASP NET Requirements in a Policy (AND Logic)
So far, we've seen multiple handlers for a single requirement (OR logic). Now let's add a second requirement to our policy.
The business team wants to restrict access based on geographic location. The API is only licensed for certain countries.
Create a new requirement and handler
public class GeographicAccessRequirement : IAuthorizationRequirement{public string[] LicensedCountries { get; } = ["en-AU", "en-IN", "en-GB", "en-US"];}public class CountryLicenseHandler : AuthorizationHandler<GeographicAccessRequirement>{protected override Task HandleRequirementAsync(AuthorizationHandlerContext context,GeographicAccessRequirement requirement){var localeClaim = context.User.FindFirst("locale");if (localeClaim != null &&requirement.LicensedCountries.Contains(localeClaim.Value, StringComparer.OrdinalIgnoreCase)){context.Succeed(requirement);}return Task.CompletedTask;}}
The handler checks the user's locale and ensures it is in the allowed list as specified by the requirement. Add the new requirement to the policy:
builder.Services.AddAuthorization(options =>{options.AddPolicy("CanAccessDetailedWeatherData", policy =>{policy.RequireAuthenticatedUser();policy.Requirements.Add(new SubscriptionTierRequirement());policy.Requirements.Add(new GeographicAccessRequirement());});});
How Multiple ASP NET Policy Requirements Are Evaluated
Now the policy has two requirements:
- SubscriptionTierRequirement - Has three handlers (premium, educational, suspended)
- GeographicAccessRequirement - Has one handler (country license)
For the policy to succeed, both requirements must succeed. This is AND logic at the requirement level.
A research user in Australia gets access (both requirements pass), but a premium user in an unlicensed region is denied (geographic requirement fails, causing the entire policy to fail).
When to Use Multiple Handlers vs Multiple Requirements
Use multiple handlers for a single requirement when:
- You want OR logic (any handler can grant access)
- Different handlers check independent conditions for the same business rule
- You want clean separation of concerns
- You might add/remove conditions dynamically
Use multiple requirements in a policy when:
- You want AND logic (all requirements must pass)
- You have distinct business rules that must all be satisfied
- Different requirements address different aspects of authorization (identity, geography, subscription, etc.)
Use context.Fail() when:
- You need to explicitly deny access regardless of other handlers
- You have blacklist scenarios (suspended users, blocked IPs, etc.)
- You want to override any
context.Succeed()calls
By combining these building blocks, you can build sophisticated authorization systems that are both powerful and maintainable.
👉 Full source code available here