ASP.NET Core MVC Mastery

Authorize Filters

2 Views Updated 5/4/2026

Authorization Filters in ASP.NET Core — Guarding the Gates

Authentication answers "Who are you?". Authorization answers "Are you allowed to do this?". In ASP.NET Core MVC, Authorization Filters are the ultimate security checkpoint—running earlier than any other filter in the pipeline to immediately reject unprivileged requests before they hit controllers, validation, or database logic.

1. WHAT Are Authorization Filters?

In the ASP.NET Core Filter Pipeline, Authorization Filters (IAsyncAuthorizationFilter) are the very first filters to execute. If an authorization filter rejects a request (e.g., setting the result to ForbidResult or ChallengeResult), the entire rest of the pipeline is short-circuited. Your model binding won't run, action filters won't run, and the controller action definitely won't run.

The [Authorize] Attribute

The [Authorize] attribute is the built-in implementation of an Authorization Filter. You can apply it at the Controller level (guarding all actions) or Action level. Use [AllowAnonymous] to carve out public paths (like login pages) inside an authorized controller.

2. REAL-TIME PRODUCTION EXAMPLES

Example 1: Role-Based Authorization

The simplest form is declarative role-checking. The framework automatically parses the user's claims to check for specific roles.

[Authorize(Roles = "Admin,SuperUser")] // OR logic: Admin OR SuperUser
public class DashboardController : Controller
{
    public IActionResult Index() => View();

    // Requires the user to have BOTH Admin AND Finance roles
    [Authorize(Roles = "Admin")]
    [Authorize(Roles = "Finance")]
    public IActionResult Revenue() => View();

    // Overrides class-level Authorize
    [AllowAnonymous] 
    public IActionResult PublicStatus() => View();
}

Example 2: Policy-Based Authorization (Enterprise Standard)

Role-based authorization breaks down in complex domains (e.g., "User can edit a document only if they are the author AND the document is unlocked"). Policies encapsulate complex logic.

// 1. Program.cs - Define Policies
builder.Services.AddAuthorization(options =>
{
    // Simple Policy: Must have specific claim
    options.AddPolicy("RequireEmployeeId", policy => 
        policy.RequireClaim("EmployeeId"));

    // Complex Policy: Must be Admin OR be in HR department and be over 21
    options.AddPolicy("CanViewPayroll", policy =>
        policy.RequireAssertion(context =>
            context.User.IsInRole("Admin") ||
            (context.User.HasClaim("Department", "HR") && 
             context.User.HasClaim("AgeCategory", "Over21"))
        ));
});
// 2. Controller - Apply the policy cleanly
[Authorize(Policy = "CanViewPayroll")]
public IActionResult Payroll() => View();

Example 3: Custom IAsyncAuthorizationFilter

If you need to validate authorization against a live database (e.g., checking if the user's API key is actively billed/unlocked), you must build a custom filter.

public class SubscriptionCheckFilter : IAsyncAuthorizationFilter
{
    private readonly ISubscriptionService _subscription;

    public SubscriptionCheckFilter(ISubscriptionService subscription) 
        => _subscription = subscription;

    public async Task OnAuthorizationAsync(AuthorizationFilterContext context)
    {
        var user = context.HttpContext.User;
        if (!user.Identity.IsAuthenticated) return;

        // Extract tenant ID from route or user claims
        var tenantId = user.FindFirst("TenantId")?.Value;

        // Custom database authorization logic
        var isActive = await _subscription.IsTenantActiveAsync(tenantId);

        if (!isActive)
        {
            // Short-circuit the request immediately with a 403 Forbidden
            context.Result = new ForbidResult();
            
            // Alternative: Redirect to payment page
            // context.Result = new RedirectToActionResult("Billing", "Tenant", null);
        }
    }
}
// Applying the custom filter
[TypeFilter(typeof(SubscriptionCheckFilter))]
public IActionResult PremiumFeature() => View();

3. Challenge vs Forbid

Challenge (401 Unauthorized)

Occurs when the user is not authenticated at all (not logged in). The authentication scheme intercepts this 401 and usually redirects the user to the /Login page.

Forbid (403 Forbidden)

Occurs when the user is logged in, but does not have the required Role/Policy. The framework returns a 403 or redirects to an "Access Denied" view. They are not sent clearly to login again, because they already are.

4. Best Practices

  • Use Policies over Roles. Hardcoding roles like [Authorize(Roles="Manager")] makes code brittle. Encapsulate it in a Policy ([Authorize(Policy="CanApproveExpense")]). Later, you can map the Manager role to that policy without touching controller code.
  • Global Authorization: In modern APIs, apply authorization globally in Program.cs via RequireAuthorization() or Fallback Policies, and use [AllowAnonymous] explicitly for the few public routes. It prevents accidental data leaks.
  • Keep filters fast: Authorization filters run on every single matching request. If you do a database lookup inside an auth filter, ensure it is heavily cached (e.g., Redis or in-memory array).

5. Interview Mastery

Q: "If an Authorization Filter and an Action Filter both throw exceptions, which order do they execute?"

Architect Answer: "Authorization Filters always run first in the MVC pipeline, immediately after Routing. If an Authorization filter (like `[Authorize]`) denies the request, it sets a Result to short-circuit the pipeline. Action Filters, Resource Filters, and Model Binding will completely skip execution. This is a critical security and performance optimization — you don't want the server expending CPU cycles binding a massive JSON payload into complex C# objects if the user doesn't even hold the appropriate JWT token to execute the action."

ASP.NET Core MVC Mastery
1. Core Framework
Introduction to ASP.NET Core MVC
MODULE 1: INTRODUCTION & ENVIRONMENT SETUP
Microsoft Web Stack Overview Evolution of ASP.NET Environment Setup
2. View Engine
Layouts & Partial Views in Razor
MODULE 2: .NET CORE FUNDAMENTALS
Core Concepts Project Structure Startup Flow Middleware Pipeline
MODULE 3: ASP.NET CORE BASICS
Creating Project CLI Commands wwwroot & Static Files
MODULE 4: MVC FUNDAMENTALS
MVC Architecture Dependency Injection (DI) Service Lifetimes
MODULE 5: DATA PASSING TECHNIQUES
ViewData vs ViewBag TempData ViewModel Pattern
MODULE 6: ROUTING
Conventional vs Attribute Routing Custom Constraints
MODULE 7: VIEWS & UI
Razor View Engine Layouts & Sections View Components
MODULE 8: ACTION RESULTS
ViewResult JsonResult RedirectResult
MODULE 9: HTML HELPERS
Form Helpers Custom HTML Helpers
MODULE 10: TAG HELPERS
Built-in Tag Helpers Custom Tag Helpers
MODULE 11: MODEL BINDING
FromQuery vs FromRoute Complex Binding
MODULE 12: VALIDATION
Data Annotations Remote Validation Fluent Validation
MODULE 13: STATE MANAGEMENT
Cookies & Sessions TempData
MODULE 14: FILTERS & SECURITY
Action Filters Authorize Filters Anti-forgery
MODULE 15: ENTITY FRAMEWORK CORE (DEEP DIVE)
DbContext Migrations LINQ Relationships
MODULE 16: DESIGN PATTERNS
Repository Pattern Unit of Work Clean Architecture
MODULE 17: FILE HANDLING
File Upload/Download PDF/Excel Generation
MODULE 18: ADVANCED ASP.NET CORE
Request Lifecycle Bundling & Minification Deployment
MODULE 19: PERFORMANCE & BEST PRACTICES
Caching Strategies Async Programming Secure Coding
MODULE 20: RAZOR PAGES (BONUS)
Razor Pages vs MVC
MODULE 21: REAL-WORLD PROJECTS (🔥 MUST DO)
E-Commerce Web Application Employee Management System