Skip to main content

.NET Authorization

Authorization is a critical aspect of web application security that determines what actions authenticated users can perform within your application. While authentication verifies who the user is, authorization decides what they're allowed to do.

In this guide, we'll explore authorization techniques in .NET applications, from simple role-based checks to more complex policy-based authorization systems.

Understanding Authorization

Authorization is the process of determining whether a user has permission to access a particular resource or perform a specific action. It happens after authentication and uses information about the authenticated user to make access control decisions.

In .NET applications, particularly ASP.NET Core, authorization can be implemented at various levels:

  1. Controller or action level - Restrict access to specific controllers or actions
  2. View level - Hide UI elements based on user permissions
  3. Resource level - Control access to specific data or resources

Basic Authorization in ASP.NET Core

ASP.NET Core provides several built-in authorization mechanisms that you can implement with minimal configuration.

Enabling Authorization

To use authorization in your ASP.NET Core application, you need to register the authorization services in your Program.cs file:

csharp
var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddControllersWithViews();

// Add authentication
builder.Services.AddAuthentication(/* authentication options */);

// Add authorization services
builder.Services.AddAuthorization();

var app = builder.Build();

// Configure middleware
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();

// These two middleware components must be added in this order
app.UseAuthentication(); // First authenticate
app.UseAuthorization(); // Then authorize

app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");

app.Run();

Simple Authorization with [Authorize] Attribute

The simplest form of authorization is using the [Authorize] attribute, which ensures that only authenticated users can access a controller or action:

csharp
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;

namespace MyApp.Controllers
{
[Authorize]
public class DashboardController : Controller
{
public IActionResult Index()
{
return View();
}

// This action can be accessed by any authenticated user
public IActionResult BasicStats()
{
return View();
}

// This action is accessible to all users, even unauthenticated ones
[AllowAnonymous]
public IActionResult PublicStats()
{
return View();
}
}
}

In this example, the entire DashboardController requires authentication except for the PublicStats action, which allows anonymous access through the [AllowAnonymous] attribute.

Role-Based Authorization

Role-based authorization allows you to grant permissions based on roles assigned to users.

Using Role-Based Authorization

You can specify required roles in the [Authorize] attribute:

csharp
[Authorize(Roles = "Administrator")]
public IActionResult AdminDashboard()
{
return View();
}

[Authorize(Roles = "Administrator,Manager")]
public IActionResult ManagementDashboard()
{
return View(); // Accessible to users in either Administrator OR Manager role
}

Requiring Multiple Roles

If a user must be in all specified roles to access an action, you need to use multiple [Authorize] attributes:

csharp
[Authorize(Roles = "Administrator")]
[Authorize(Roles = "Manager")]
public IActionResult SensitiveOperation()
{
return View(); // Only accessible to users who are BOTH Administrator AND Manager
}

Checking Roles in Code

Sometimes you need to make authorization decisions within your action methods based on more complex logic:

csharp
public IActionResult ManageUsers()
{
if (!User.IsInRole("Administrator") && !User.IsInRole("UserManager"))
{
return Forbid(); // Return 403 Forbidden if user doesn't have appropriate role
}

// User has appropriate role, continue with the action
var users = _userService.GetAllUsers();
return View(users);
}

Policy-Based Authorization

Policy-based authorization offers more flexibility than simple role checks, allowing you to define complex authorization rules.

Configuring Authorization Policies

You can define policies in the Program.cs file:

csharp
builder.Services.AddAuthorization(options =>
{
// Simple policy requiring a specific role
options.AddPolicy("RequireAdminRole", policy =>
policy.RequireRole("Administrator"));

// Policy requiring multiple claims
options.AddPolicy("ContentEditor", policy =>
policy.RequireClaim("Permission", "EditContent")
.RequireClaim("Department", "Marketing", "Content"));

// Policy with custom requirements
options.AddPolicy("SeniorEmployeesOnly", policy =>
policy.RequireAssertion(context =>
context.User.HasClaim(c =>
c.Type == "EmploymentDate" &&
DateTime.Parse(c.Value) < DateTime.Now.AddYears(-5))));
});

Applying Policy-Based Authorization

You can apply policies using the [Authorize] attribute:

csharp
[Authorize(Policy = "RequireAdminRole")]
public IActionResult AdminPanel()
{
return View();
}

[Authorize(Policy = "ContentEditor")]
public IActionResult EditContent()
{
return View();
}

[Authorize(Policy = "SeniorEmployeesOnly")]
public IActionResult SeniorStaffMeeting()
{
return View();
}

Creating Custom Authorization Requirements and Handlers

For more complex authorization scenarios, you can create custom requirements and handlers.

Define a Requirement

First, create a requirement by implementing IAuthorizationRequirement:

csharp
public class MinimumAgeRequirement : IAuthorizationRequirement
{
public int MinimumAge { get; }

public MinimumAgeRequirement(int minimumAge)
{
MinimumAge = minimumAge;
}
}

Create a Handler

Next, implement a handler for the requirement:

csharp
public class MinimumAgeHandler : AuthorizationHandler<MinimumAgeRequirement>
{
protected override Task HandleRequirementAsync(
AuthorizationHandlerContext context,
MinimumAgeRequirement requirement)
{
if (!context.User.HasClaim(c => c.Type == "DateOfBirth"))
{
// If user has no date of birth claim, fail the requirement
return Task.CompletedTask;
}

var dateOfBirthClaim = context.User.FindFirst(c => c.Type == "DateOfBirth");
var dateOfBirth = DateTime.Parse(dateOfBirthClaim.Value);
var age = DateTime.Today.Year - dateOfBirth.Year;

// Adjust age if birthday hasn't occurred yet this year
if (dateOfBirth.Date > DateTime.Today.AddYears(-age))
{
age--;
}

if (age >= requirement.MinimumAge)
{
context.Succeed(requirement);
}

return Task.CompletedTask;
}
}

Register and Use Custom Policy

Register your requirement handler and create a policy that uses it:

csharp
// In Program.cs
builder.Services.AddSingleton<IAuthorizationHandler, MinimumAgeHandler>();

builder.Services.AddAuthorization(options =>
{
options.AddPolicy("AtLeast18", policy =>
policy.Requirements.Add(new MinimumAgeRequirement(18)));

options.AddPolicy("AtLeast21", policy =>
policy.Requirements.Add(new MinimumAgeRequirement(21)));
});

Then use it like any other policy:

csharp
[Authorize(Policy = "AtLeast18")]
public IActionResult AdultContent()
{
return View();
}

[Authorize(Policy = "AtLeast21")]
public IActionResult PurchaseAlcohol()
{
return View();
}

Resource-Based Authorization

Sometimes, authorization depends on the specific resource being accessed. For example, a user may only be allowed to edit documents they created.

Implementing Resource-Based Authorization

csharp
public class DocumentAuthorizationHandler : 
AuthorizationHandler<OperationAuthorizationRequirement, Document>
{
protected override Task HandleRequirementAsync(
AuthorizationHandlerContext context,
OperationAuthorizationRequirement requirement,
Document resource)
{
if (requirement.Name == "Edit" || requirement.Name == "Delete")
{
if (context.User.GetUserId() == resource.CreatedById)
{
// Allow document creators to edit and delete
context.Succeed(requirement);
}
}

if (requirement.Name == "View")
{
if (resource.IsPublic || context.User.GetUserId() == resource.CreatedById)
{
// Anyone can view public documents or documents they created
context.Succeed(requirement);
}
}

return Task.CompletedTask;
}
}

Using the IAuthorizationService

To use resource-based authorization in your controllers:

csharp
public class DocumentsController : Controller
{
private readonly IAuthorizationService _authorizationService;
private readonly IDocumentRepository _documentRepository;

public DocumentsController(
IAuthorizationService authorizationService,
IDocumentRepository documentRepository)
{
_authorizationService = authorizationService;
_documentRepository = documentRepository;
}

public async Task<IActionResult> Edit(int id)
{
var document = _documentRepository.GetDocument(id);
if (document == null)
{
return NotFound();
}

var authResult = await _authorizationService.AuthorizeAsync(
User, document, new OperationAuthorizationRequirement { Name = "Edit" });

if (!authResult.Succeeded)
{
return Forbid();
}

// User is authorized to edit this document
return View(document);
}
}

Authorization in Razor Views

Authorization in ASP.NET Core can be extended to Razor views to conditionally display UI elements:

html
@using Microsoft.AspNetCore.Authorization
@inject IAuthorizationService AuthorizationService

<div class="container">
<h1>Document: @Model.Title</h1>

@if (User.Identity.IsAuthenticated)
{
<div class="actions">
@if (User.IsInRole("Editor") || User.IsInRole("Administrator"))
{
<a asp-action="Edit" asp-route-id="@Model.Id" class="btn btn-primary">Edit</a>
}

@if ((await AuthorizationService.AuthorizeAsync(User, Model, "Delete")).Succeeded)
{
<a asp-action="Delete" asp-route-id="@Model.Id" class="btn btn-danger">Delete</a>
}
</div>
}
</div>

Practical Example: Building a Complete Authorization System

Let's build a more comprehensive example of a blog application with multiple authorization layers:

csharp
// Define custom operations
public static class Operations
{
public static OperationAuthorizationRequirement Create =
new() { Name = "Create" };
public static OperationAuthorizationRequirement Read =
new() { Name = "Read" };
public static OperationAuthorizationRequirement Update =
new() { Name = "Update" };
public static OperationAuthorizationRequirement Delete =
new() { Name = "Delete" };
public static OperationAuthorizationRequirement Publish =
new() { Name = "Publish" };
}

// Blog post model
public class BlogPost
{
public int Id { get; set; }
public string Title { get; set; }
public string Content { get; set; }
public string AuthorId { get; set; }
public bool IsPublished { get; set; }
public DateTime CreatedAt { get; set; }
public DateTime? PublishedAt { get; set; }
}

// BlogPost authorization handler
public class BlogPostAuthorizationHandler :
AuthorizationHandler<OperationAuthorizationRequirement, BlogPost>
{
protected override Task HandleRequirementAsync(
AuthorizationHandlerContext context,
OperationAuthorizationRequirement requirement,
BlogPost resource)
{
var userId = context.User.FindFirstValue(ClaimTypes.NameIdentifier);

switch (requirement.Name)
{
case "Read":
// Anyone can read published posts
if (resource.IsPublished)
{
context.Succeed(requirement);
}
// Authors can read their own unpublished posts
else if (resource.AuthorId == userId)
{
context.Succeed(requirement);
}
// Editors and Admins can read all posts
else if (context.User.IsInRole("Editor") || context.User.IsInRole("Administrator"))
{
context.Succeed(requirement);
}
break;

case "Create":
// Writers, Editors and Admins can create posts
if (context.User.IsInRole("Writer") ||
context.User.IsInRole("Editor") ||
context.User.IsInRole("Administrator"))
{
context.Succeed(requirement);
}
break;

case "Update":
// Authors can update their own posts
if (resource.AuthorId == userId)
{
context.Succeed(requirement);
}
// Editors and Admins can update any post
else if (context.User.IsInRole("Editor") || context.User.IsInRole("Administrator"))
{
context.Succeed(requirement);
}
break;

case "Delete":
// Authors can delete their own unpublished posts
if (resource.AuthorId == userId && !resource.IsPublished)
{
context.Succeed(requirement);
}
// Editors can delete any post
else if (context.User.IsInRole("Editor"))
{
context.Succeed(requirement);
}
// Admins can delete anything
else if (context.User.IsInRole("Administrator"))
{
context.Succeed(requirement);
}
break;

case "Publish":
// Only Editors and Admins can publish posts
if (context.User.IsInRole("Editor") || context.User.IsInRole("Administrator"))
{
context.Succeed(requirement);
}
break;
}

return Task.CompletedTask;
}
}

// Register the handler in Program.cs
builder.Services.AddSingleton<IAuthorizationHandler, BlogPostAuthorizationHandler>();

// Using the authorization in a controller
public class BlogController : Controller
{
private readonly IBlogRepository _blogRepository;
private readonly IAuthorizationService _authorizationService;

public BlogController(
IBlogRepository blogRepository,
IAuthorizationService authorizationService)
{
_blogRepository = blogRepository;
_authorizationService = authorizationService;
}

public async Task<IActionResult> View(int id)
{
var post = _blogRepository.GetBlogPost(id);
if (post == null)
{
return NotFound();
}

var authResult = await _authorizationService.AuthorizeAsync(
User, post, Operations.Read);

if (!authResult.Succeeded)
{
return Forbid();
}

return View(post);
}

[HttpPost]
public async Task<IActionResult> Publish(int id)
{
var post = _blogRepository.GetBlogPost(id);
if (post == null)
{
return NotFound();
}

var authResult = await _authorizationService.AuthorizeAsync(
User, post, Operations.Publish);

if (!authResult.Succeeded)
{
return Forbid();
}

post.IsPublished = true;
post.PublishedAt = DateTime.UtcNow;

_blogRepository.UpdateBlogPost(post);

return RedirectToAction("View", new { id = post.Id });
}
}

Summary

In this guide, we've covered:

  1. Basic authorization concepts in .NET applications
  2. Simple authorization using the [Authorize] attribute
  3. Role-based authorization for controlling access based on user roles
  4. Policy-based authorization for more complex authorization rules
  5. Custom authorization requirements and handlers
  6. Resource-based authorization for fine-grained access control
  7. Implementing authorization in Razor views
  8. A practical example of a complete authorization system for a blog application

Properly implementing authorization is critical for application security. By combining different authorization approaches, you can create secure, flexible systems that precisely control what users can access and do within your application.

Additional Resources

Exercises

  1. Create a simple ASP.NET Core application with controllers that require different authorization levels.
  2. Implement a custom authorization policy that checks if a user has verified their email address.
  3. Build a document management system where users can only edit documents they created, but administrators can edit any document.
  4. Implement a multi-tenant application where users can only access data from their own organization.
  5. Create a role-based menu system that shows different navigation options depending on the user's roles.


If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)