In the previous article, we implemented Role-Based Authorization using the [Authorize(Roles = "...")] attribute. It worked well for scenarios like allowing only Admins to manage products or Customers to place orders.
However, real-world applications often require more than just roles.
For example:
A customer can cancel an order only if it hasn't been shipped.
A premium member can access exclusive products.
An employee must belong to the Sales department to view sales reports.
A user must be at least 18 years old to access certain services.
These rules cannot be expressed using roles alone.
This is where Policy-Based Authorization becomes useful.
In this article, you'll learn what policies are, why they are needed, how to configure them in ASP.NET Core, and how they work internally.
![Policy-Based Authorization in ASP.NET Core Web API Beyond Roles]()
What Is Policy-Based Authorization?
Policy-Based Authorization allows you to define authorization rules based on one or more requirements.
Instead of checking only the user's role, a policy can evaluate:
A policy acts as a reusable authorization rule that can be applied to one or many API endpoints.
Why Do We Need Policies?
Let's continue with our SecureShop API.
Suppose the business introduces a Premium Membership.
Only premium members can access exclusive products.
Would creating another role be the right solution?
For example:
Customer
PremiumCustomer
GoldCustomer
PlatinumCustomer
As the application grows, managing roles becomes difficult.
Instead, we can keep the user's role as Customer and add another claim:
Membership = Premium
Now the authorization decision becomes:
Is the user a Premium member?
This is a perfect use case for a policy.
Roles vs Policies
Both roles and policies are used for authorization, but they solve different problems.
| Role-Based Authorization | Policy-Based Authorization |
|---|
| Checks user roles | Checks rules or requirements |
| Simple to configure | More flexible |
| Best for broad permissions | Best for business rules |
| Example: Admin | Example: Premium Member |
A good practice is:
Creating a Claim for Membership
Suppose the JWT contains the following claim:
new Claim("Membership", "Premium")
Now the authenticated user carries this information inside the JWT.
After authentication, ASP.NET Core makes it available through HttpContext.User.
Step 1: Register a Policy
Open Program.cs.
Add a policy while registering authorization.
builder.Services.AddAuthorization(options =>
{
options.AddPolicy("PremiumOnly", policy =>
{
policy.RequireClaim("Membership", "Premium");
});
});
This creates a policy named PremiumOnly.
The rule is simple:
The user must have a claim named Membership with the value Premium.
Step 2: Apply the Policy
Now protect an endpoint using the policy.
[Authorize(Policy = "PremiumOnly")]
[HttpGet("premium-products")]
public IActionResult GetPremiumProducts()
{
return Ok();
}
Only users whose JWT contains:
Membership = Premium
can access this endpoint.
How Policy Evaluation Works
Let's understand the internal flow.
Client
│
│ JWT
▼
Authentication Middleware
│
▼
Validate JWT
│
▼
Create ClaimsPrincipal
│
▼
Policy Evaluation
│
▼
Membership = Premium ?
│
Yes │ No
│
▼
Controller Executes
Notice that policy evaluation happens after authentication.
If the user is not authenticated, the policy is never evaluated.
Practical Example
Let's continue with the SecureShop API.
Suppose two customers log in.
Customer 1
Name : John
Membership : Premium
Customer 2
Name : Alex
Membership : Standard
Now both users request:
GET /api/premium-products
The endpoint is protected using:
[Authorize(Policy = "PremiumOnly")]
John's Request
John is authenticated.
↓
Membership claim = Premium.
↓
Policy requirement satisfied.
↓
API returns:
200 OK
Alex's Request
Alex is authenticated.
↓
Membership claim = Standard.
↓
Policy requirement fails.
↓
API returns:
403 Forbidden
Although Alex is authenticated, he doesn't meet the policy requirements.
Combining Roles and Policies
ASP.NET Core allows both approaches to work together.
For example:
[Authorize(Roles = "Admin", Policy = "PremiumOnly")]
In this case, the user must:
Be an Admin
Have Premium membership
Both conditions must be satisfied.
This makes authorization very powerful for complex business scenarios.
When Should You Use Policies?
Policies are ideal when authorization depends on business rules rather than user categories.
Examples include:
Premium membership
Subscription plans
Department name
Employee type
Country or region
License status
Account verification
These rules can change over time without creating additional roles.
Common Mistakes
Mistake 1: Creating Roles for Everything
Roles should represent broad categories such as:
Avoid creating dozens of roles for business-specific conditions.
Use policies instead.
Mistake 2: Hardcoding Business Rules Inside Controllers
Some developers write authorization checks directly inside action methods.
For example:
if(User.FindFirst("Membership")?.Value != "Premium")
{
return Forbid();
}
This duplicates logic across controllers.
Policies centralize authorization rules and make them reusable.
Mistake 3: Forgetting Required Claims
A policy that requires a claim will fail if that claim isn't included in the JWT.
Always ensure the token contains the claims your policies depend on.
Mistake 4: Confusing Roles with Claims
A role is just one type of claim.
Policies can evaluate any claim—not only roles.
This makes policies much more flexible.
Key Takeaways
Policy-Based Authorization provides more flexibility than Role-Based Authorization.
Policies evaluate business rules using claims, roles, or other requirements.
Register policies in Program.cs using AddAuthorization().
Apply policies with [Authorize(Policy = "...")].
Policies are ideal for requirements such as membership levels, departments, or subscription plans.
Use roles for broad user categories and policies for business-specific access rules.
Our SecureShop API can now authenticate users, authorize them by roles, and enforce business rules using policies. In the next article, we'll take authorization one step further by creating a Custom Authorization Requirement and Authorization Handler, allowing us to implement authorization rules that cannot be expressed using built-in role or claim checks alone.