Authorization

15 questions found

What is the difference between role-based and claims-based authorization in ASP.NET Core?

Beginner
Role-based authorization ([Authorize(Roles = "Admin")]) checks whether the user's ClaimsPrincipal has a role claim matching one of the specified roles -- a simple, coarse-grained check. Claims-based authorization checks for the presence (and optionally the value) of any specific claim type, offering finer-grained control since claims can represent arbitrary attributes beyond just roles (like a subscription tier, department, or permission level).
// Role-based
[Authorize(Roles = "Admin")]
public IActionResult DeleteUser(int id) { ... }

// Claims-based (via policy)
builder.Services.AddAuthorization(options => {
    options.AddPolicy("PremiumOnly", policy => policy.RequireClaim("subscription", "premium"));
});
Real-world example An e-commerce platform uses role-based authorization for admin functions (Admin, Support roles) but claims-based policies for feature access (checking a 'subscription' claim's value) since subscription tiers aren't naturally modeled as roles.

Common follow-ups: How is a role technically implemented as just a special kind of claim internally?;When does claims-based authorization become preferable to role-based?

Authentication;Configuration & Options Pattern

How do you define and apply policy-based authorization for requirements more complex than a simple role or claim check?

Intermediate
AddAuthorization registers named policies built from one or more IAuthorizationRequirement objects (or simple RequireClaim/RequireRole/RequireAssertion builder calls for straightforward cases), applied to endpoints via [Authorize(Policy = "PolicyName")] -- letting you express arbitrarily complex authorization logic (combinations of claims, custom business rules) as a reusable, named, centrally-defined policy rather than scattering ad-hoc checks throughout controller code.
builder.Services.AddAuthorization(options => {
    options.AddPolicy("MinimumAge", policy =>
        policy.RequireAssertion(context =>
            context.User.HasClaim(c => c.Type == "age" && int.Parse(c.Value) >= 18)));
});

[Authorize(Policy = "MinimumAge")]
public IActionResult ViewRestrictedContent() { ... }
Real-world example An age-restricted content endpoint uses a custom MinimumAge policy evaluating a claim's numeric value, expressing logic that a simple role or single-claim check couldn't represent as cleanly.

Common follow-ups: How do you write a fully custom IAuthorizationHandler for even more complex logic?;How do multiple requirements within one policy combine (AND vs OR)?

Authentication;RESTful Web APIs & Controllers

How do you implement resource-based authorization, where the authorization decision depends on the specific object being accessed, not just the user's general permissions?

Advanced
Resource-based authorization uses IAuthorizationService.AuthorizeAsync(user, resource, requirement) directly within your action/endpoint (rather than a declarative attribute, since the resource instance isn't known until the object is loaded), combined with a custom IAuthorizationHandler<TRequirement, TResource> that inspects both the user and the specific resource (e.g., checking if the user is the resource's owner) to make the decision.
public class OwnerAuthorizationHandler : AuthorizationHandler<OwnerRequirement, Document> {
    protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, OwnerRequirement requirement, Document resource) {
        if (resource.OwnerId == context.User.FindFirst("sub")?.Value)
            context.Succeed(requirement);
        return Task.CompletedTask;
    }
}

// Usage in an endpoint:
var document = await _repository.GetAsync(id);
var result = await _authorizationService.AuthorizeAsync(User, document, "DocumentOwner");
if (!result.Succeeded) return Forbid();
Real-world example A document management system ensures users can only edit documents they own by checking resource-based authorization against the actual loaded Document entity's OwnerId, something a declarative [Authorize] attribute alone couldn't express since it has no access to the specific resource instance.

Common follow-ups: Why can't resource-based authorization be expressed with a declarative attribute alone?;How do multiple resource-based handlers for the same requirement combine?

Entity Framework Core & Data Access;Filters

What is the difference between AuthorizeAttribute's behavior returning 401 versus 403, and what determines which one a client receives?

Intermediate
A 401 Unauthorized response means the request lacks valid authentication credentials entirely (or they failed to validate) -- the user isn't recognized at all. A 403 Forbidden means the user IS successfully authenticated but lacks sufficient permissions for the specific requested action -- ASP.NET Core's authorization middleware automatically returns the correct one based on whether HttpContext.User.Identity.IsAuthenticated is true or false when an authorization check fails.
// Unauthenticated request to a protected endpoint -> 401
// GET /api/admin/users (no Authorization header at all)

// Authenticated but insufficiently privileged request -> 403
// GET /api/admin/users
// Authorization: Bearer <valid token for a non-admin user>
Real-world example A frontend application distinguishes between a 401 (redirect to login page, since the session may have expired) and a 403 (show a permanent 'access denied' message, since re-authenticating wouldn't help) based on this status code difference.

Common follow-ups: Can you customize what triggers 401 versus 403 beyond the default authenticated/unauthenticated distinction?;How should a client's UI handle each status code differently?

Authentication;Error Handling

How would you implement a custom IAuthorizationRequirement and IAuthorizationHandler for a business rule like 'only during business hours'?

Advanced
Define a class implementing IAuthorizationRequirement (often just a marker with no logic itself), then an AuthorizationHandler<TRequirement> subclass implementing HandleRequirementAsync that evaluates the actual business rule (checking DateTime.Now against configured business hours) and calls context.Succeed(requirement) if satisfied -- registered via services.AddScoped<IAuthorizationHandler, BusinessHoursHandler>() and referenced in a policy.
public class BusinessHoursRequirement : IAuthorizationRequirement { }

public class BusinessHoursHandler : AuthorizationHandler<BusinessHoursRequirement> {
    protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, BusinessHoursRequirement requirement) {
        var now = DateTime.Now.TimeOfDay;
        if (now >= TimeSpan.FromHours(9) && now <= TimeSpan.FromHours(17))
            context.Succeed(requirement);
        return Task.CompletedTask;
    }
}

builder.Services.AddAuthorization(o => o.AddPolicy("BusinessHoursOnly", p => p.Requirements.Add(new BusinessHoursRequirement())));
Real-world example A high-risk financial transaction endpoint restricts execution to business hours using a custom requirement/handler pair, ensuring transfers above a threshold can only be initiated when support staff are available to respond to issues.

Common follow-ups: How would you inject additional dependencies (like a holiday calendar service) into the handler?;What happens if a policy has requirements that can never succeed due to a bug?

Configuration & Options Pattern;Filters

How do you apply a global authorization policy requiring authentication by default for every endpoint, with explicit opt-out for public ones?

Intermediate
Setting options.FallbackPolicy = new AuthorizationPolicyBuilder().RequireAuthenticatedUser().Build() makes authentication mandatory for every endpoint that doesn't have explicit [Authorize] or [AllowAnonymous] metadata, flipping the default from 'public unless marked otherwise' to 'protected unless explicitly marked public' -- a much safer default posture that prevents accidentally leaving a new endpoint unintentionally unauthenticated.
builder.Services.AddAuthorization(options => {
    options.FallbackPolicy = new AuthorizationPolicyBuilder()
        .RequireAuthenticatedUser()
        .Build();
});

[AllowAnonymous]  // must explicitly opt out for public endpoints
[HttpGet("health")]
public IActionResult Health() => Ok();
Real-world example A security audit recommends switching from the default 'opt-in to protection' model to a fallback policy requiring authentication everywhere by default, after discovering a newly added endpoint had been accidentally left completely unprotected for weeks.

Common follow-ups: What endpoints commonly need [AllowAnonymous] even with this fallback policy enabled?;How does this interact with health check endpoints that orchestrators need to reach unauthenticated?

Health Checks;ASP.NET Core Middleware & Request Pipeline

How does the AuthorizationMiddlewareResultHandler let you customize the response returned when authorization fails, beyond the default 401/403?

Advanced
Implementing a custom IAuthorizationMiddlewareResultHandler and registering it lets you intercept authorization failures to return a custom response shape (like a ProblemDetails-formatted JSON error explaining exactly which policy failed) instead of the framework's bare default response, useful for APIs wanting consistent, informative error responses across every kind of failure including authorization.
public class CustomAuthResultHandler : IAuthorizationMiddlewareResultHandler {
    private readonly AuthorizationMiddlewareResultHandler _default = new();
    public async Task HandleAsync(RequestDelegate next, HttpContext context, AuthorizationPolicy policy, PolicyAuthorizationResult authorizeResult) {
        if (!authorizeResult.Succeeded) {
            context.Response.StatusCode = 403;
            await context.Response.WriteAsJsonAsync(new { title = "Access denied", policy = policy.AuthenticationSchemes });
            return;
        }
        await _default.HandleAsync(next, context, policy, authorizeResult);
    }
}

builder.Services.AddSingleton<IAuthorizationMiddlewareResultHandler, CustomAuthResultHandler>();
Real-world example An API standardizing all error responses (including authorization failures) on the ProblemDetails format registers a custom result handler so a failed policy check produces the same consistent JSON error shape as any other error type in the application.

Common follow-ups: How does this interact with the global exception handling middleware for other error types?;What's the risk of leaking too much detail about why authorization failed?

Global Exception Handling & Middleware;RESTful Web APIs & Controllers

How do you restrict access to a minimal API endpoint or endpoint group using authorization, given minimal APIs don't use controller attributes?

Intermediate
Minimal API endpoints use the fluent .RequireAuthorization() extension method (optionally passing a policy name), applicable to individual endpoints or entire route groups via MapGroup, mirroring the declarative [Authorize] attribute's behavior but in the minimal API's method-chaining style.
app.MapGet("/admin/users", () => GetUsers())
   .RequireAuthorization("AdminOnly");

// Applying to an entire group at once
var adminGroup = app.MapGroup("/admin").RequireAuthorization("AdminOnly");
adminGroup.MapGet("/users", () => GetUsers());
adminGroup.MapDelete("/users/{id}", (int id) => DeleteUser(id));
Real-world example An admin-only minimal API route group applies RequireAuthorization once at the group level, automatically protecting every endpoint mapped within that group without repeating the authorization call on each individual route.

Common follow-ups: How would you allow anonymous access to one endpoint within an otherwise-protected group?;Does RequireAuthorization support the same policy names as the [Authorize] attribute?

Controllers vs Minimal APIs;Endpoint Metadata Route Constraints & Templates

How would you implement hierarchical or nested permission checks, like an organization admin having access to all of their organization's resources?

Advanced
A common approach models permissions hierarchically in your data (an OrganizationId claim plus a resource's own OrganizationId field) and implements a resource-based authorization handler that checks whether the user's organization matches (or, for a super-admin role, bypasses the check entirely) -- combining role-based (super-admin bypass) and resource-based (organization membership match) checks within a single handler for a complete hierarchical permission model.
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, ResourceAccessRequirement requirement, Project resource) {
    if (context.User.IsInRole("SuperAdmin")) { context.Succeed(requirement); return Task.CompletedTask; }
    var userOrgId = context.User.FindFirst("org_id")?.Value;
    if (userOrgId == resource.OrganizationId.ToString() && context.User.IsInRole("OrgAdmin"))
        context.Succeed(requirement);
    return Task.CompletedTask;
}
Real-world example A multi-tenant project management SaaS lets organization admins manage every project within their own organization while a platform-level SuperAdmin role bypasses organizational boundaries entirely for support purposes, both expressed in one authorization handler.

Common follow-ups: How do you efficiently check organization membership without a database call on every single request?;How would you extend this model to support team-level (not just org-level) permissions?

Multiple Inheritance & MRO;Entity Framework Core & Data Access

What is the purpose of the [AllowAnonymous] attribute, and how does it interact with a controller-level [Authorize] attribute?

Intermediate
[AllowAnonymous] applied to a specific action overrides an [Authorize] attribute applied at the controller level, letting most of a controller's actions require authentication while specifically exempting individual actions (like a public health check or a login endpoint that logically lives in an otherwise-protected AccountController) -- action-level attributes always take precedence over controller-level ones for this purpose.
[Authorize]
public class AccountController : ControllerBase {
    [AllowAnonymous]
    [HttpPost("login")]
    public IActionResult Login(LoginDto dto) { ... }  // publicly accessible despite controller-level [Authorize]

    [HttpGet("profile")]
    public IActionResult Profile() { ... }  // requires authentication (inherits controller-level attribute)
}
Real-world example An AccountController marked with a blanket [Authorize] attribute still allows anonymous access specifically to its Login and Register actions via [AllowAnonymous], since requiring authentication to log in would be a contradiction.

Common follow-ups: Does [AllowAnonymous] interact with a global FallbackPolicy the same way it does with controller-level [Authorize]?;What happens with multiple [Authorize] attributes stacked on the same action?

ASP.NET Core Middleware & Request Pipeline;Controllers vs Minimal APIs

Showing 1–10 of 15