ASP.NET Core authorization: roles, claims, policies, resource-based checks, EF Core tenant filters

ASP.NET Core Interview Q&A November 13, 2026 csharp

Resource-based authorization is for when the decision depends on the object itself (which customer the order belongs to).

csharp
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
// ======================================================================
// Q: Roles, claims, policies: how do you implement authorization?
//
// Resource-based authorization is for when the decision depends on the object
// itself (which customer the order belongs to).
// Multi-tenancy: a global query filter in EF Core, so that no query "forgets"
// the tenant.
// Roles for coarse-grained checks ("who can approve"), policies and claims for
// rules, resource-based authorization for ownership. And always an integration
// test that verifies customer A cannot see customer B's data (Q13).
// ======================================================================

// Policies and a secure-by-default fallback policy
builder.Services.AddAuthorizationBuilder()
    // Secure by default: every endpoint requires login unless it has [AllowAnonymous]
    .SetFallbackPolicy(new AuthorizationPolicyBuilder().RequireAuthenticatedUser().Build())
    .AddPolicy("ApproveOrders", p => p.RequireRole("PurchasingManager"))
    .AddPolicy("B2BCustomer",   p => p.RequireClaim("customer_id"));

[Authorize(Policy = "ApproveOrders")]
[HttpPost("{id:int}/approve")]
public async Task<IActionResult> Approve(int id, CancellationToken ct) { /* ... */ }

// Resource-based authorization: the order must belong to the caller's customer
public record SameCustomerRequirement : IAuthorizationRequirement;

public class SameCustomerHandler : AuthorizationHandler<SameCustomerRequirement, Order>
{
    protected override Task HandleRequirementAsync(
        AuthorizationHandlerContext ctx, SameCustomerRequirement req, Order order)
    {
        if (ctx.User.GetCustomerId() == order.CustomerId) ctx.Succeed(req);
        return Task.CompletedTask;
    }
}
builder.Services.AddSingleton<IAuthorizationHandler, SameCustomerHandler>();

// In the endpoint
var auth = await authorizationService.AuthorizeAsync(User, order, new SameCustomerRequirement());
if (!auth.Succeeded) return NotFound();                 // 404, not 403 (Q1)

// Multi-tenancy: EF Core global query filter per tenant
public class AppDbContext(DbContextOptions<AppDbContext> o, ITenantContext tenant) : DbContext(o)
{
    protected override void OnModelCreating(ModelBuilder mb) =>
        mb.Entity<Order>().HasQueryFilter(x => x.TenantId == tenant.TenantId);
}
// tenant.TenantId comes from the token's claims, NEVER from a header or query string