--- 

title: "Authorization"
date: '2026-09-29'
excerpt: "Learn how to implement authorization in your .NET API."
---


Previous step answered *“who is this?”* (authentication). This step answers *“are they allowed?”* (authorization).

Types of authorization in ASP.NET Core:
- Simple Authorization
- Role-based Authorization
- Policy-based Authorization
- Reuirements & Handlers
- Resouce-based Authorization
- View-based Authorization
- Authorzation Filters
- .....

<Figure
  src="/learn/dotnet-api/05-auth/authorization-specturm.png"
  alt="Types of authorization in ASP.NET Core"
  caption="Types of authorization in ASP.NET Core"
  width="800"
/>

<Guide title="Setups for Simple Authorization" numbered headingLevel={2} stepHeadingLevel={3}>
  <GuideStep title="1. Register Authorization Services">

```csharp title="src/Services/MyApp.Api/Presentation/DependencyInjection.cs" showLineNumbers {3,5-12}
// Inside AddApi(...):
services.AddJwtAuthentication();       // Previously
services.AddAuthorizationPolicies();   // this step 

private static IServiceCollection AddAuthorizationPolicies(this IServiceCollection services)
{
    services.AddAuthorization(); 
    return services;
}
```

  </GuideStep>
  
  <GuideStep title="2. Enable authorization middleware">
    


Pipeline — after `UseAuthentication`:

```csharp title="src/Services/MyApp.Api/Infrastructure/WebApplicationExtensions.cs" showLineNumbers {2}
app.UseAuthentication();   // Previously
app.UseAuthorization();    // this step
```

  </GuideStep>
  
<GuideStep title="3. Protect public endpoints">

```csharp title="src/Services/MyApp.Api/Features/Auth/AdminUsers/AdminUsersEndpoint.cs" showLineNumbers {7}
public sealed class AdminUsersEndpoint : ICarterModule
{
    public void AddRoutes(IEndpointRouteBuilder app)
    {
        var group = app.MapGroup("/auth/admin/users")
            .WithTags("AuthAdmin")
            .RequireAuthorization();
    }
}
```

Now when we call the endpoint, we will get a 401 Unauthorized response.

<Figure
  src="/learn/dotnet-api/05-auth/authorization-onchallange.png"
    alt="OnChallenge event getting called"
    caption="OnChallenge event getting called"
  width="800"
/>


</GuideStep>

</Guide>

<Guide title="Role-based Authorization" numbered headingLevel={2} stepHeadingLevel={3}>

<Figure
  src="/learn/dotnet-api/05-auth/role-based-auth-1.png"
  alt="Role-based authorization"
  caption="Role-based authorization"
  width="800"
/>

Exactly. `RequireAuthorization()` gives you **basic authentication-based authorization**. For **role-based authorization**, you tell ASP.NET Core which role is required.


<GuideStep title="1. Define the role requirement (demo)">
  First, define the `admin` role requirement  to your Carter route:

```csharp showLineNumbers {7-8}
public sealed class AdminUsersEndpoint : ICarterModule
{
    public void AddRoutes(IEndpointRouteBuilder app)
    {
        var group = app.MapGroup("/auth/admin/users")
            .WithTags("AuthAdmin")
            //.RequireAuthorization() 
            .RequireAuthorization(policy => policy.RequireRole("Admin"));

        group.MapGet("/", () =>
        {
            return Results.Ok("Admin users");
        });
    }
}
```

Now the request must contain an authenticated user **with the `admin` role**.

</GuideStep>


<GuideStep title="2. Where does the role come from? - claims (demo)">

The role needs to be added to the claims of the JWT token for a user to be authorized:
  
```csharp showLineNumbers {5}
var claims = new[]
{
    new Claim(ClaimTypes.Name, user.UserName!),
    new Claim(ClaimTypes.Email, user.Email!),
    new Claim(ClaimTypes.Role, "Admin")
};
```

Then ASP.NET Core can evaluate:

```csharp
user.IsInRole("Admin")
```

**Request flow**

```text
JWT
 │
 ├── name = John
 ├── email = john@example.com
 └── role = Admin
       │
       ▼
ClaimsPrincipal
       │
       ▼
RequireRole("Admin")
       │
   ┌───┴────┐
   │        │
 Admin    not Admin
   │        │
   ▼        ▼
 200       403
```

→ User must be **authenticated + have the `Admin` role**.
</GuideStep>

### Multiple roles

<Figure
  src="/learn/dotnet-api/05-auth/multi-role-based-auth-1.png"
  alt="Multiple roles"
  caption="Multiple roles"
  width="800"
/>

For multiple roles, you can do:


<Mark type="circle">**OR**</Mark> — user can have either role

```csharp
.RequireAuthorization(policy =>
    policy.RequireRole(Roles.Admin, Roles.Manager));
```

This means:

```text
Admin OR Manager
```

So:

| User            | Access |
| --------------- | ------ |
| Admin           | ✅      |
| Manager         | ✅      |
| Admin + Manager | ✅      |
| User            | ❌      |

---

<Mark type="circle">**AND**</Mark> — user must have both roles

If you need:

```text
Admin AND Manager
```

add two role requirements:

```csharp
.RequireAuthorization(policy =>
{
    policy.RequireRole(Roles.Admin);
    policy.RequireRole(Roles.Manager);
});
```

Now:

| User            | Access |
| --------------- | ------ |
| Admin only      | ❌      |
| Manager only    | ❌      |
| Admin + Manager | ✅      |
| User            | ❌      |

<Mark type="underline">Easy rule to remember</Mark>

```csharp
RequireRole("Admin", "Manager")
```

➡️ **OR**

```csharp
RequireRole("Admin");
RequireRole("Manager");
```

### How our TokenService creates the token
     
```csharp title="src/Services/MyApp.Api/Features/Auth/TokenService.cs" showLineNumbers {15-16}
//....
    public string CreateAccessToken(User user, IEnumerable<string> roles)
    {
        var now = _clock.GetUtcNow();
        var expires = now.AddMinutes(_jwt.AccessTokenMinutes);

        var claims = new List<Claim>
        {
            new(JwtRegisteredClaimNames.Sub, user.Id.ToString()),//~sub = userId
            new(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString("N")),
            new(JwtRegisteredClaimNames.Iat, EpochTime.GetIntDate(now.UtcDateTime).ToString(), ClaimValueTypes.Integer64),
            new(JwtRegisteredClaimNames.Email, user.Email),
            new(AuthClaims.TokenVersion, user.PermissionVersion.ToString()),
        };
        claims.AddRange(roles.Select(r => new Claim(AuthClaims.Role, r))); 
        //~new Claim("role", "X"), new Claim("role", "Y")....
        

        //...
    }
//....
```    
    

</Guide>


## Policy-based Authorization

Policy-based authorization is where you combine `roles` + `claims` + `authentication requirements `into a named policy.

<Figure
  src="/learn/dotnet-api/05-auth/policy-based-auth-0.png"
  alt="Policy-based authorization"
  caption="Policy-based authorization"
  width="800"
/>

<Guide numbered headingLevel={2} stepHeadingLevel={3}>

<Figure
  src="/learn/dotnet-api/05-auth/policy-based-auth-1.png"
  alt="Policy-based authorization steps"
  caption="Policy-based authorization steps"
  width="800"
/>

    <GuideStep title="1. Define the policy">
    
```csharp
builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("management", policy =>
    {
        policy
            .RequireRole("management")
            .RequireClaim("email")
            .RequireClaim("country", "Sweden", "Denmark")
            .RequireClaim("JobTitle", "Manager");
    });

    options.AddPolicy("sales", policy =>
    {
        policy.RequireRole("management", "sales");
    });

    options.AddPolicy("customer", policy =>
    {
        policy.RequireAuthenticatedUser();
    });
});
```     

   
<Mark type="underline">**Management policy**</Mark>

Different requirements are combined with **AND**:

```text
Authenticated
     AND
Role = management
     AND
email claim exists
     AND
country = Sweden OR Denmark
     AND
JobTitle = Manager
```

<Mark type="underline">**Sales policy**</Mark>

Here the roles are **OR**:

```text
management OR sales
```

So either role satisfies the requirement.


<Mark type="underline" color="default">**Customer policy**</Mark>

 
This simply means:

```text
User must be authenticated
```

No specific role or claim is required.

---

</GuideStep>


<GuideStep title="2. Adding the claims to your JWT">
            
We need to add the claims that our policies actually check.

For example:

```csharp showLineNumbers {17-19}
var claims = new List<Claim>
{
    // Identity
    new(JwtRegisteredClaimNames.Sub, user.Id.ToString()),
    // JWT ID
    new(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString("N")),
    // Issued-at
    new(
        JwtRegisteredClaimNames.Iat,
        EpochTime.GetIntDate(now.UtcDateTime).ToString(),
        ClaimValueTypes.Integer64
    ),
    // User information
    new(JwtRegisteredClaimNames.Email, user.Email!),
    
    // Authorization
    new(ClaimTypes.Role, "management"),
    new("country", "Sweden"),
    new("JobTitle", "Manager"),

};
```

Now your JWT effectively contains:



```text
sub          = 123
email        = john@example.com
role         = management
country      = Sweden
JobTitle     = Manager
TokenVersion = 5
```

And the `management` policy can evaluate those values.

---

One important detail: role claim

For:

```csharp
.RequireRole("management")
```

you should normally put the role into the JWT using:

```csharp
new Claim(ClaimTypes.Role, "management")
```

not:

```csharp
new Claim("role", "management")
```

unless you've explicitly configured the JWT handler's `RoleClaimType` to `"role"`.


</GuideStep>

<GuideStep title="3. Applying the policy">


Once the policies are registered, attach them to routes by name.


```csharp showLineNumbers {5,11,17}
group.MapGet("/management", () =>
{
    return Results.Ok("Management area");
})
.RequireAuthorization("management");

group.MapGet("/sales", () =>
{
    return Results.Ok("Sales area");
})
.RequireAuthorization("sales");

group.MapGet("/customer", () =>
{
    return Results.Ok("Customer area");
})
.RequireAuthorization("customer");
```

The important distinction is:

```csharp
.RequireAuthorization("management")
```

means:

> **Use the policy named `management`.**

It does **not** mean the string `"management"` is automatically a role.

The policy itself determines what `"management"` means.

---

</GuideStep>
</Guide>


## Custom Requirements & Authorization Handlers

Sometimes you need to add custom requirements to your policies.

For example:

```csharp showLineNumbers {6-8}
options.AddPolicy("amdin", policy =>
{
    // built-in requirement (automically handled by ASP.NET Core)
    policy.RequireRole("admin", "management");

    // custom requirement (you need to create and register)
    policy.Requirements.Add(new EnhancedSecurityRequirement());
    policy.Requirements.Add(new WorkingHoursRequirement(9, 18));
});
```

Here you have **three requirements**:

1. User must have `admin` or `management` role.
2. `EnhancedSecurityRequirement` must pass.
3. `WorkingHoursRequirement(9, 18)` must pass.

Because they are separate requirements, they are effectively **AND** conditions.

---

<Guide title="Steps to create a custom requirement" numbered headingLevel={3} stepHeadingLevel={4}>

ASP.NET Core's policy-based authorization framework rests on three interfaces:

| Building Block | Interface | Description |
| --- | --- | --- |
| **Requirement** | `IAuthorizationRequirement` | A marker interface representing "what must be true" |
| **Handler** | `IAuthorizationHandler` | The logic that evaluates whether a requirement is met |
| **Dynamic Policy** | `IAuthorizationPolicyProvider` | Dynamically builds policies from requirements |

The flow is elegant: a policy contains requirements. Requirements have handlers. Handlers inspect the user, the resource, and the context, then call context.Succeed(requirement) if access should be granted.

<GuideStep title="1. Implement `IAuthorizationRequirement`">
 A requirement is usually a small class implementing `IAuthorizationRequirement`.

<Figure
  src="/learn/dotnet-api/05-auth/custom-req.png"
  alt="Custom requirement"
  caption="Custom requirement"
  width="800"
/>

WorkingHoursRequirement class:

```csharp showLineNumbers {1}
public sealed class WorkingHoursRequirement : IAuthorizationRequirement
{
    public int StartHour { get; }
    public int EndHour { get; }

    public WorkingHoursRequirement(int startHour, int endHour)
    {
        StartHour = startHour;
        EndHour = endHour;
    }
}
```

</GuideStep>

<GuideStep title="2. Create the `AuthorizationHandler` handler">

The handler contains the actual authorization logic.

For a given requirement, each handler can:

* **Approve** — call `context.Succeed(requirement)`.
* **Fail** — call `context.Fail()`.
* **Ignore** — do nothing.

<Figure
  src="/learn/dotnet-api/05-auth/custom-handler.png"
  alt="Custom handler"
  caption="Custom handler"
  width="600"
/>

A single requirement can have **multiple handlers**.

For example, `EnhancedSecurityRequirement` can be satisfied by checking both MFA and the client's IP address:

<CodeTabs>

```csharp title="MfaHandler.cs" showLineNumbers {1,5,11,13}
public sealed class MfaHandler: AuthorizationHandler<EnhancedSecurityRequirement>
{
    protected override Task HandleRequirementAsync(
        AuthorizationHandlerContext context,
        EnhancedSecurityRequirement requirement)
    {
        var hasMfa = context.User.HasClaim("mfa", "true");

        if (hasMfa)
        {
            context.Succeed(requirement);
        }

        return Task.CompletedTask;
    }
}
```

```csharp title="IpAddressHandler.cs" showLineNumbers {1,5}
public sealed class IpAddressHandler: AuthorizationHandler<EnhancedSecurityRequirement>
{
    protected override Task HandleRequirementAsync(
        AuthorizationHandlerContext context,
        EnhancedSecurityRequirement requirement)
    {
        var ipAddress = context.Resource switch
        {
            HttpContext httpContext =>
                httpContext.Connection.RemoteIpAddress?.ToString(),

            _ => null
        };

        if (ipAddress == "192.168.1.100")
        {
            context.Succeed(requirement);
        }

        return Task.CompletedTask;
    }
}
```

```csharp title="WorkingHoursHandler.cs" showLineNumbers {1,12,16,18}
public sealed class WorkingHoursHandler : AuthorizationHandler<WorkingHoursRequirement>
{
    protected override Task HandleRequirementAsync(
        AuthorizationHandlerContext context,
        WorkingHoursRequirement requirement)
    {
        var hour = DateTime.UtcNow.Hour;

        if (hour >= requirement.StartHour &&
            hour < requirement.EndHour)
        {
            context.Succeed(requirement);
        }
        else
        {
            context.Fail();
        }
        return Task.CompletedTask;
    }
}
```
</CodeTabs>
</GuideStep>

<GuideStep title="3. Register the handler">


Register the handler with DI:

```csharp
// single shared instance
builder.Services.AddSingleton<IAuthorizationHandler,IpAddressHandler>();
builder.Services.AddSingleton<IAuthorizationHandler,MfaHandler>();
```

Or:

```csharp
// fresh instance for each request
builder.Services.Transient<IAuthorizationHandler,WorkingHoursHandler>();
```

Which lifetime you use depends on what the handler depends on.

---

</GuideStep>


</Guide>


## Permission-based Authorization (with custom requirements & handlers)

Roles answer "what group is this user in?" Permissions answer "what can this user *do*?" — `Permissions.Products.View`, `Permissions.Users.Ban`, and so on.

The pattern we'll use:

1. Store permissions in the database (assigned via roles).
2. Embed those permissions as claims when `TokenService` creates the JWT.
3. On every request, the authorization handler checks claims only — **no DB hit**.

<Guide title="Level 1: Simple Permission Handler" numbered headingLevel={3} stepHeadingLevel={4}>
Start with a basic permission system where permissions are already present as claims on the JWT.

<GuideStep title="Designing a Permission System">

Define all permissions as constants in a static class — these strings are what you store in the DB, embed in the JWT, and check in policies:

```csharp
public static class Permissions
{
    public static class Products
    {
        public const string View = "Permissions.Products.View";
        public const string Create = "Permissions.Products.Create";
        public const string Edit = "Permissions.Products.Edit";
        public const string Delete = "Permissions.Products.Delete";
    }
    public static class Users
    {
        public const string View = "Permissions.Users.View";
        public const string Ban = "Permissions.Users.Ban";
    }
    public static class Invoices
    {
        public const string View = "Permissions.Invoices.View";
        public const string Edit = "Permissions.Invoices.Edit";
        public const string Delete = "Permissions.Invoices.Delete";
    }
}
```

</GuideStep>

<GuideStep title="The Requirement">

```csharp showLineNumbers {1}
public sealed class PermissionRequirement : IAuthorizationRequirement
{
    public string Permission { get; }
    public PermissionRequirement(string permission)
    {
        if (string.IsNullOrWhiteSpace(permission))
            throw new ArgumentException("Permission cannot be empty", nameof(permission));

        Permission = permission;
    }
}
```  

</GuideStep>

<GuideStep title="The Handler">

The handler never touches the database. Auth middleware has already decoded the JWT into `context.User` — we just read the `AuthClaims.Permission` claims.

```csharp showLineNumbers {1,7-9}
public sealed class PermissionAuthorizationHandler : AuthorizationHandler<PermissionRequirement>
{
    protected override Task HandleRequirementAsync(
        AuthorizationHandlerContext context,
        PermissionRequirement requirement)
    {
        var hasPermission = context.User.HasClaim(c =>
            c.Type == AuthClaims.Permission &&
            c.Value.Equals(requirement.Permission, StringComparison.OrdinalIgnoreCase));

        if (hasPermission)
            context.Succeed(requirement);
        else
            context.Fail(); // hard fail — fine for a single-requirement policy

        return Task.CompletedTask;
    }
}
```

</GuideStep>

<GuideStep title="Registration">


```csharp showLineNumbers {4-5,7-8,10-11,14}
// Program.cs
builder.Services.AddAuthorization(options =>
{
    options.AddPolicy(Permissions.Products.View, policy =>
        policy.Requirements.Add(new PermissionRequirement(Permissions.Products.View)));

    options.AddPolicy(Permissions.Products.Create, policy =>
        policy.Requirements.Add(new PermissionRequirement(Permissions.Products.Create)));

    options.AddPolicy(Permissions.Products.Delete, policy =>
        policy.Requirements.Add(new PermissionRequirement(Permissions.Products.Delete)));
});
// Register the handler (singleton because it's stateless — no DbContext)
builder.Services.AddSingleton<IAuthorizationHandler, PermissionAuthorizationHandler>();
```
    
</GuideStep>

<GuideStep title="Usage">

```csharp showLineNumbers {3,6,9}
app.MapGet("/api/products", async (IProductService products) =>
    await products.GetAllAsync())
    .RequireAuthorization(Permissions.Products.View);
app.MapPost("/api/products", async (CreateProductRequest request, IProductService products) =>
    await products.CreateAsync(request))
    .RequireAuthorization(Permissions.Products.Create);
app.MapDelete("/api/products/{id:int}", async (int id, IProductService products) =>
    await products.DeleteAsync(id))
    .RequireAuthorization(Permissions.Products.Delete);
```  

</GuideStep>
</Guide>

<Guide title="Level 2: Database-Driven & JWT-Embedded Permissions" numbered headingLevel={3} stepHeadingLevel={4}>

In production, we usually keep <Mark type="underline" color="tip">roles and permissions in the database</Mark> (source of truth for admins), load them when <Mark type="underline" color="tip">issuing a token, and embed them in the JWT as claims</Mark>. After that, authorization stays claim-based — no per-request DB lookup.

<GuideStep title="Permission Entity">

```csharp
public sealed class Permission
{
    public Guid Id { get; set; }
    public string Name { get; set; } = string.Empty; // e.g. "Permissions.Products.Delete"
    public string Description { get; set; } = string.Empty;
}

public sealed class Role
{
    public Guid Id { get; set; }
    public string Name { get; set; } = string.Empty; // e.g. "Admin", "Finance"
}

public sealed class User
{
    public Guid Id { get; set; }
    public string Email { get; set; } = string.Empty;
    public int PermissionVersion { get; set; } = 1;
}

// Join tables (many-to-many)
public sealed class UserRole
{
    public Guid UserId { get; set; }
    public Guid RoleId { get; set; }
}

public sealed class RolePermission
{
    public Guid RoleId { get; set; }
    public Guid PermissionId { get; set; }
}
```
    
</GuideStep>

<GuideStep title="Admin management endpoints">

Those entities only matter if admins can assign them. Expose small management endpoints (Admin-only) that write `UserRoles` / `RolePermissions`. The next login (or token refresh) will pick up the new grants when `TokenService` embeds permissions.

```csharp
public sealed class AdminPermissionsEndpoint : ICarterModule
{
    public void AddRoutes(IEndpointRouteBuilder app)
    {
        var group = app.MapGroup("/auth/admin")
            .WithTags("AuthAdmin")
            .RequireAuthorization(policy => policy.RequireRole("Admin"));

        group.MapPost("/assign-permission", async (
            AssignPermissionRequest request,
            AppDbContext db) =>
        {
            var role = await db.Roles.FindAsync(request.RoleId);
            var permission = await db.Permissions
                .FirstOrDefaultAsync(p => p.Name == request.Permission);

            if (role is null || permission is null)
                return Results.NotFound("Role or permission not found");

            db.RolePermissions.Add(new RolePermission
            {
                RoleId = role.Id,
                PermissionId = permission.Id
            });
            await db.SaveChangesAsync();

            return Results.Ok("Permission assigned");
        });

        group.MapPost("/assign-role", async (
            AssignRoleRequest request,
            AppDbContext db) =>
        {
            var user = await db.Users.FindAsync(request.UserId);
            var role = await db.Roles.FindAsync(request.RoleId);

            if (user is null || role is null)
                return Results.NotFound("User or role not found");

            db.UserRoles.Add(new UserRole
            {
                UserId = user.Id,
                RoleId = role.Id
            });
            await db.SaveChangesAsync();

            return Results.Ok("Role assigned");
        });
    }
}

public sealed record AssignPermissionRequest(Guid RoleId, string Permission);
public sealed record AssignRoleRequest(Guid UserId, Guid RoleId);
```

Flow:

1. Admin calls `POST /auth/admin/assign-permission` or `assign-role` → DB updated.
2. User logs in / refreshes → `TokenService` loads roles + permissions into the JWT.
3. Protected endpoints authorize from those claims (no DB on every request).

</GuideStep>

<GuideStep title="Embed permissions in TokenService">

When you create the access token, query the user's permissions and add each one as an `AuthClaims.Permission` claim — same idea as embedding roles earlier.

```csharp title="TokenService.cs" showLineNumbers {15-16}
public string CreateAccessToken(User user, IEnumerable<string> roles, IEnumerable<string> permissions)
{
    var now = _clock.GetUtcNow();
    var expires = now.AddMinutes(_jwt.AccessTokenMinutes);

    var claims = new List<Claim>
    {
        new(JwtRegisteredClaimNames.Sub, user.Id.ToString()),
        new(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString("N")),
        new(JwtRegisteredClaimNames.Iat, EpochTime.GetIntDate(now.UtcDateTime).ToString(), ClaimValueTypes.Integer64),
        new(JwtRegisteredClaimNames.Email, user.Email),
        new(AuthClaims.TokenVersion, user.PermissionVersion.ToString()),
    };

    claims.AddRange(roles.Select(r => new Claim(AuthClaims.Role, r)));
    claims.AddRange(permissions.Select(p => new Claim(AuthClaims.Permission, p)));

    // sign & return JWT...
}
```

At login (or refresh), load roles + permissions from the DB **once**, then pass them in:

```csharp showLineNumbers {18}
var user = await _db.Users.FirstOrDefaultAsync(u => u.Email == request.Email);

var roles = await (
    from ur in _db.UserRoles
    join r in _db.Roles on ur.RoleId equals r.Id
    where ur.UserId == user.Id
    select r.Name
).Distinct().ToListAsync();

var permissions = await (
    from ur in _db.UserRoles
    join rp in _db.RolePermissions on ur.RoleId equals rp.RoleId
    join p in _db.Permissions on rp.PermissionId equals p.Id
    where ur.UserId == user.Id
    select p.Name
).Distinct().ToListAsync();

var token = _tokenService.CreateAccessToken(user, roles, permissions);
```

Your JWT payload now looks like:

```text
sub         = 123
email       = john@example.com
role        = Finance
permission  = Permissions.Invoices.View
permission  = Permissions.Invoices.Edit
permission  = Permissions.Products.View
```

💡 Result: authorization handlers see those claims on `context.User` with **zero DB hits** per request. Claim type (`AuthClaims.Permission`) and values must match what `PermissionRequirement` checks.

<Callout variant="warning" title="Stale permissions">
If you change a user's permissions in the DB, existing tokens keep the old claims until they expire or are refreshed. Use short-lived access tokens, force refresh on permission changes, or bump `PermissionVersion` / revoke tokens when roles change.
</Callout>

</GuideStep>

</Guide>

<Guide title="Level 3: Dynamic Permission Policy Provider" numbered headingLevel={3} stepHeadingLevel={4}>
Level 1 already gave us `Permissions`, `PermissionRequirement`, and `PermissionAuthorizationHandler`. The annoying part is manually calling `AddPolicy` for every permission. Level 3 fixes that.

<GuideStep title="🧩 Create a Dynamic Permission Policy Provider">

If we want a dynamic policy, something like:

```csharp
[Authorize(Policy = Permissions.Products.Delete)]
// → [Authorize(Policy = "Permissions.Products.Delete")]
```

We can't manually define hundreds of those! Instead, we'll teach ASP.NET Core to generate permission policies on the fly.

We already have two of the three pieces from Level 1:
- 🧩 `PermissionRequirement` – represents the permission we need to check.
- 🧩 `PermissionAuthorizationHandler` – checks the `AuthClaims.Permission` claim on the JWT.
- 🧩 <Mark type="circle"> `PermissionPolicyProvider` </Mark> – dynamically creates policies when ASP.NET Core requests them.


We'll make a custom `IAuthorizationPolicyProvider` that dynamically creates policies based on permission strings.

Add this file: `PermissionPolicyProvider.cs`

```csharp  showLineNumbers {12-24}
public class PermissionPolicyProvider : IAuthorizationPolicyProvider
{
    private readonly DefaultAuthorizationPolicyProvider _fallbackPolicyProvider;
    public PermissionPolicyProvider(IOptions<AuthorizationOptions> options)
    {
        _fallbackPolicyProvider = new DefaultAuthorizationPolicyProvider(options);
    }
    public Task<AuthorizationPolicy?> GetDefaultPolicyAsync()
        => _fallbackPolicyProvider.GetDefaultPolicyAsync();
    public Task<AuthorizationPolicy?> GetFallbackPolicyAsync()
        => _fallbackPolicyProvider.GetFallbackPolicyAsync();
    public Task<AuthorizationPolicy?> GetPolicyAsync(string policyName)
    {
        // If policy starts with "Permissions.", treat it as a permission policy
        if (policyName.StartsWith("Permissions.", StringComparison.OrdinalIgnoreCase))
        {
            var policy = new AuthorizationPolicyBuilder()
                .AddRequirements(new PermissionRequirement(policyName))
                .Build();
            return Task.FromResult<AuthorizationPolicy?>(policy);
        }
        // fallback for other policies
        return _fallbackPolicyProvider.GetPolicyAsync(policyName);
    }
}
```
💡 How it works:

- <Mark type="underline" color="tip">When ASP.NET Core sees `[Authorize(Policy = "Permissions.Products.Delete")]`, it asks the provider: "Hey, do you know this policy?"</Mark>
- The provider detects the `"Permissions."` prefix and automatically creates a new policy requiring that permission.
- The `PermissionAuthorizationHandler` checks whether the JWT has a matching `AuthClaims.Permission` claim.

Therefore, we no longer need to manually call <Mark type="crossed-off" color="danger"> `AddPolicy(...)`</Mark> in `Program.cs`.

Make sure the strings you embed at login match these policy names (e.g. store / claim `"Permissions.Products.Delete"`, not just `"Products.Delete"`).

</GuideStep>
<GuideStep title="Register the Policy Provider">
    
The handler is already registered from Level 1. Add the policy provider, and you can drop the manual `AddPolicy(...)` calls — the provider creates them on demand.

```csharp
builder.Services.AddSingleton<IAuthorizationHandler, PermissionAuthorizationHandler>(); // from Level 1
builder.Services.AddSingleton<IAuthorizationPolicyProvider, PermissionPolicyProvider>();
```

</GuideStep>

</Guide>



## Resource-based Authorization

Permission policies (Level 1–3) answer:

> "Does this user have `Permissions.Invoices.Edit`?"

A Finance **role** answers:

> "Is this user in the Finance group?"

Resource-based authorization answers:

> "Given that — can they access *this particular invoice*?"

Two Finance users can share the same role and the same `Permissions.Invoices.*` claims. What differs is the **resource**: who owns the invoice, which department it belongs to, and so on. You still embed permissions at login; the handler does **not** hit the DB for grants. It only needs the invoice the endpoint already loaded.

<Figure
    src="/learn/dotnet-api/05-auth/resource-based-auth-1.png"
    alt="Resource-based authorization"
    caption="Resource-based authorization"
    width="800"
/>

Same permission claim. The decision depends on the **relationship between the user and that invoice**.

You *can* do that check by hand inside the endpoint — load the invoice, compare owner to `sub`, forbid if they don't match:

```csharp
app.MapGet("/api/invoices/{id:guid}", async (
    Guid id,
    ClaimsPrincipal user,
    IInvoiceRepository invoices) =>
{
    // 1. Load invoice from database
    var invoice = await invoices.GetByIdAsync(id);
    if (invoice is null)
        return Results.NotFound();

    // 2. Current user id from JWT (sub)
    var currentUserId = user.GetUserId(); // AuthClaims extension

    // 3. Ensure the user owns the invoice
    if (invoice.OwnerId != currentUserId)
        return Results.Forbid();

    return Results.Ok(invoice);
})
.RequireAuthorization("finance"); // named policy: RequireRole("Finance") — see below
```

That works for a demo, but it doesn't scale — every endpoint reimplements the same ownership rules. Prefer a reusable resource handler (below).

<Guide title="Invoice ownership + permission claims" numbered headingLevel={3} stepHeadingLevel={4}>

<GuideStep title="The invoice resource">

Model the resource with the fields authorization will need — typically id, owner, and department:

```csharp showLineNumbers {1-7}
public interface IOwnedResource
{
    Guid Id { get; }
    Guid OwnerId { get; }
}

public sealed class Invoice : IOwnedResource
{
    public Guid Id { get; set; }
    public Guid OwnerId { get; set; }
    public string Department { get; set; } = "Finance";
    public decimal Amount { get; set; }
    public string Status { get; set; } = default!;
}
```

`IOwnedResource` keeps the handler reusable for other owned types later (documents, tickets, …). Invoice is the concrete resource type we care about here.

</GuideStep>

<GuideStep title="The requirement">

Reuse permission strings from Level 1 (`Permissions.Invoices.Edit`, etc.). The requirement says which permission this operation needs, and whether ownership matters:

```csharp showLineNumbers {4}
public sealed class ResourcePermissionRequirement : IAuthorizationRequirement
{
    public string Permission { get; }
    public bool RequireOwnership { get; }

    public ResourcePermissionRequirement(string permission, bool requireOwnership = true)
    {
        if (string.IsNullOrWhiteSpace(permission))
            throw new ArgumentException("Permission cannot be empty", nameof(permission));

        Permission = permission;
        RequireOwnership = requireOwnership;
    }
}
```

- `Permissions.Invoices.View` → often `requireOwnership: false` (any Finance user with View can read invoices in scope).
- `Permissions.Invoices.Edit` / `Delete` → `requireOwnership: true` (must own the invoice, unless a manager override).

</GuideStep>

<GuideStep title="The resource handler">

`AuthorizationHandler<TRequirement, TResource>` receives the **invoice**. Permission check = JWT claims (same as Level 1). Ownership check = compare `OwnerId` to `sub`. Optionally allow a Finance manager role to bypass ownership.

```csharp showLineNumbers {16-23}
public sealed class ResourcePermissionHandler
    : AuthorizationHandler<ResourcePermissionRequirement, IOwnedResource>
{
    protected override Task HandleRequirementAsync(
        AuthorizationHandlerContext context,
        ResourcePermissionRequirement requirement,
        IOwnedResource resource)
    {
        var hasPermission = context.User.HasClaim(c =>
            c.Type == AuthClaims.Permission &&
            c.Value.Equals(requirement.Permission, StringComparison.OrdinalIgnoreCase));

        if (!hasPermission)
            return Task.CompletedTask;

        if (requirement.RequireOwnership)
        {
            var userId = context.User.GetUserId(); // AuthClaims extension → JWT sub
            var isFinanceManager = context.User.IsInRole("FinanceManager");

            if (resource.OwnerId != userId && !isFinanceManager)
                return Task.CompletedTask;
        }

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

💡 Same rules as permission-based auth:

- Claims come from `TokenService` (`AuthClaims.Permission`).
- No `DbContext` in the handler — zero DB hits for authorization.
- On miss we **don't** call `context.Fail()` (unlike Level 1's simple handler). Returning without `Succeed` lets other handlers for the same requirement still run; call `Fail()` only when you want to short-circuit.

`Department` is on the model so you can extend later (e.g. match a `department` claim) without changing how permissions are embedded in the JWT.

</GuideStep>

<GuideStep title="Registration + Finance policy">

```csharp
builder.Services.AddSingleton<IAuthorizationHandler, ResourcePermissionHandler>();

builder.Services.AddAuthorization(options =>
{
    // Endpoint-level gate: authenticated + Finance role.
    // Do NOT put the resource/ownership requirement here — that needs the loaded invoice.
    options.AddPolicy("finance", policy =>
        policy.RequireRole("Finance"));
});
```

You might be tempted to combine role + ownership in one policy:

```csharp
// ❌ Don't do this for resource ownership
options.AddPolicy("finance", policy =>
{
    policy.RequireRole("Finance");
    policy.Requirements.Add(new ResourcePermissionRequirement(Permissions.Invoices.Edit));
});
```

That fails for resource auth: `.RequireAuthorization("finance")` runs **before** the handler, so ASP.NET Core has no `Invoice` instance to pass into `ResourcePermissionHandler`. Split the concerns:

| Layer | What it checks | When |
| --- | --- | --- |
| `.RequireAuthorization("finance")` | Authenticated + `Finance` role | Before endpoint runs |
| `auth.AuthorizeAsync(user, invoice, …)` | Permission claim + ownership | After you load the invoice |

</GuideStep>

<GuideStep title="Authorize against the loaded invoice">

Load the invoice, then authorize against that instance. Keep the Finance policy on the route — `AuthorizeAsync` alone does **not** replace it.

```csharp showLineNumbers {5,12-15,28,35-38,51,58-63}
app.MapPut("/api/invoices/{id:guid}", async (
    Guid id,
    UpdateInvoiceRequest request,
    IInvoiceRepository invoices,
    IAuthorizationService auth,
    ClaimsPrincipal user) =>
{
    var invoice = await invoices.GetByIdAsync(id);
    if (invoice is null)
        return Results.NotFound();

    var result = await auth.AuthorizeAsync(
        user,
        invoice,
        new ResourcePermissionRequirement(Permissions.Invoices.Edit));

    if (!result.Succeeded)
        return Results.Forbid();

    var updated = await invoices.UpdateAsync(id, request);
    return Results.Ok(updated);
})
.RequireAuthorization("finance");

app.MapDelete("/api/invoices/{id:guid}", async (
    Guid id,
    IInvoiceRepository invoices,
    IAuthorizationService auth,
    ClaimsPrincipal user) =>
{
    var invoice = await invoices.GetByIdAsync(id);
    if (invoice is null)
        return Results.NotFound();

    var result = await auth.AuthorizeAsync(
        user,
        invoice,
        new ResourcePermissionRequirement(Permissions.Invoices.Delete));

    if (!result.Succeeded)
        return Results.Forbid();

    await invoices.DeleteAsync(id);
    return Results.NoContent();
})
.RequireAuthorization("finance");

app.MapGet("/api/invoices/{id:guid}", async (
    Guid id,
    IInvoiceRepository invoices,
    IAuthorizationService auth,
    ClaimsPrincipal user) =>
{
    var invoice = await invoices.GetByIdAsync(id);
    if (invoice is null)
        return Results.NotFound();

    var result = await auth.AuthorizeAsync(
        user,
        invoice,
        new ResourcePermissionRequirement(
            Permissions.Invoices.View,
            requireOwnership: false));

    if (!result.Succeeded)
        return Results.Forbid();

    return Results.Ok(invoice);
})
.RequireAuthorization("finance");
```


The important call:

```csharp
await auth.AuthorizeAsync(
    user,
    invoice, // ← the resource
    new ResourcePermissionRequirement(Permissions.Invoices.Edit));
```

```text
Request
   ↓
Authentication (JWT)
   ↓
.RequireAuthorization("finance")  → Role = Finance?
   ↓
Load Invoice
   ↓
AuthorizeAsync(user, invoice, requirement)
   ↓
Has AuthClaims.Permission == Permissions.Invoices.Edit?
   ↓
OwnerId == sub  (or FinanceManager)?
   ↓
Allow / Forbid
```

</GuideStep>

</Guide>



<Callout variant="info" title="MyApp reference implementation">
The monolith demo on branch <code>auth</code> wires these pieces under
<code>Features/Auth/Authorization/</code>, seeds <code>Permission</code> / <code>RolePermission</code>,
gates Products mutations with <code>Permissions.Products.*</code>, and demos invoices + WorkingHours /
EnhancedSecurity. Multi-service YARP + shared <code>AddMyAppAuth</code> continues in the next lesson.
</Callout>

## References

- [🧠 Implementing a Custom Authorization Handler in ASP.NET Core 10 | Permission Authorization](https://blog.stackademic.com/implementing-a-custom-authorization-handler-in-asp-net-core-10-permission-authorization-4cbf2a9330a2)
- [Implementing a Custom Authorization Handler in ASP.NET](https://medium.com/@mariammaurice/implementing-a-custom-authorization-handler-in-asp-net-937b1e190f3f#-part-2--building-the-permission-authorization-system)