.NET Authorization Techniques
Introduction
Authorization is a critical security concept that determines what actions users can perform after they've been authenticated. While authentication answers the question "Who are you?", authorization answers "What are you allowed to do?". In .NET applications, implementing proper authorization prevents unauthorized access to sensitive resources, functions, and data.
This guide explores various authorization techniques available in the .NET ecosystem, from basic role-based access control to more sophisticated policy-based approaches. By the end of this tutorial, you'll understand how to secure your .NET applications by implementing appropriate authorization strategies.
Authorization vs. Authentication
Before diving into authorization techniques, let's clarify the distinction between authentication and authorization:
- Authentication: Verifies the identity of a user or system (who they are)
- Authorization: Determines what resources a user or system can access and what actions they can perform (what they can do)
Think of it like this: Authentication is showing your ID at the entrance of a building, while authorization is determining which rooms you can enter once inside.
Basic Authorization Concepts in .NET
Role-Based Authorization
Role-based authorization is the simplest and most common form of authorization. Users are assigned roles (like "Admin", "Manager", or "User"), and access to resources is granted based on these roles.
Implementation in .NET Core
Let's see how to implement role-based authorization in an ASP.NET Core application:
// In Startup.cs or Program.cs (for .NET 6+)
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(/* Authentication setup */);
services.AddAuthorization(options =>
{
options.AddPolicy("AdminsOnly", policy => policy.RequireRole("Administrator"));
options.AddPolicy("UsersAndAdmins", policy =>
policy.RequireRole("User", "Administrator"));
});
}
Then, you can apply these policies to controllers or action methods:
[Authorize(Policy = "AdminsOnly")]
public class AdminController : Controller
{
public IActionResult Index()
{
return View();
}
}
// Or at the action level
public class UserController : Controller
{
[Authorize(Policy = "UsersAndAdmins")]
public IActionResult ViewUserData()
{
return View();
}
}
Example Output
When a user with the "Administrator" role accesses AdminController
, they'll see the requested page. However, if a user with only the "User" role tries to access it, they'll receive a 403 Forbidden response or be redirected to an access denied page.
Claims-Based Authorization
Claims-based authorization provides more granular control than roles. A claim is a name-value pair that represents what a user is, not what they can do.
Implementation Example
// Configure claims policy
services.AddAuthorization(options =>
{
options.AddPolicy("EmployeeOnly", policy =>
policy.RequireClaim("EmployeeNumber"));
options.AddPolicy("SeniorEmployees", policy =>
policy.RequireClaim("EmployeeLevel", "Senior", "Principal"));
});
Application in controller:
[Authorize(Policy = "SeniorEmployees")]
public class SalaryController : Controller
{
public IActionResult ViewTeamSalaries()
{
// Only users with EmployeeLevel claim of "Senior" or "Principal" can access
return View();
}
}
Policy-Based Authorization
Policy-based authorization is the most flexible approach, allowing you to implement custom authorization logic.
Custom Policy Example
// Define policy requirements
public class MinimumAgeRequirement : IAuthorizationRequirement
{
public int MinimumAge { get; }
public MinimumAgeRequirement(int minimumAge)
{
MinimumAge = minimumAge;
}
}
// Implement a handler for the requirement
public class MinimumAgeHandler : AuthorizationHandler<MinimumAgeRequirement>
{
protected override Task HandleRequirementAsync(
AuthorizationHandlerContext context,
MinimumAgeRequirement requirement)
{
// No DateOfBirth claim? Auto-fail
if (!context.User.HasClaim(c => c.Type == "DateOfBirth"))
{
return Task.CompletedTask;
}
// Get date of birth from the claim
var dateOfBirth = Convert.ToDateTime(
context.User.FindFirst(c => c.Type == "DateOfBirth").Value);
// Calculate age
var userAge = DateTime.Today.Year - dateOfBirth.Year;
if (dateOfBirth > DateTime.Today.AddYears(-userAge))
{
userAge--;
}
// Check if age requirement is met
if (userAge >= requirement.MinimumAge)
{
context.Succeed(requirement);
}
return Task.CompletedTask;
}
}
Registration and usage:
// Register the handler and policy
services.AddSingleton<IAuthorizationHandler, MinimumAgeHandler>();
services.AddAuthorization(options =>
{
options.AddPolicy("AtLeast18", policy =>
policy.Requirements.Add(new MinimumAgeRequirement(18)));
});
// Usage in controller
[Authorize(Policy = "AtLeast18")]
public class AdultContentController : Controller
{
// Only users 18 or older can access
}
Resource-Based Authorization
Sometimes, authorization depends not just on the user but also on the specific resource being accessed. For example, a user might be allowed to edit their own documents but not others'.
Resource Authorization Example
public class DocumentAuthorizationHandler :
AuthorizationHandler<OperationAuthorizationRequirement, Document>
{
protected override Task HandleRequirementAsync(
AuthorizationHandlerContext context,
OperationAuthorizationRequirement requirement,
Document resource)
{
if (requirement.Name == "Edit")
{
// Allow editing if the user is the owner of the document
if (context.User.FindFirst("UserId")?.Value == resource.OwnerId)
{
context.Succeed(requirement);
}
}
return Task.CompletedTask;
}
}
// Using it in a controller
public class DocumentController : Controller
{
private readonly IAuthorizationService _authorizationService;
public DocumentController(IAuthorizationService authorizationService)
{
_authorizationService = authorizationService;
}
public async Task<IActionResult> Edit(int id)
{
var document = await _documentRepository.GetDocumentAsync(id);
if (document == null)
{
return NotFound();
}
var authorizationResult = await _authorizationService.AuthorizeAsync(
User, document, "Edit");
if (!authorizationResult.Succeeded)
{
return new ForbidResult();
}
// Continue with edit operation
return View(document);
}
}
Authorization in Razor Pages
For Razor Pages, you can apply authorization in several ways:
// In Startup.cs or Program.cs (for .NET 6+)
services.AddRazorPages(options =>
{
options.Conventions.AuthorizePage("/Admin/Index", "AdminsOnly");
options.Conventions.AuthorizeFolder("/Members", "MembersOnly");
});
Or directly in the Razor Page model:
[Authorize(Policy = "MembersOnly")]
public class MemberContentModel : PageModel
{
// Page logic here
}
Implementing Authorization in Blazor
Blazor introduces its own authorization concepts, particularly for client-side applications:
Blazor Server
In Blazor Server, you can use the standard ASP.NET Core authorization:
// In a Blazor component
@page "/sensitive-data"
@attribute [Authorize(Policy = "DataAccess")]
<h1>Sensitive Data</h1>
@code {
// Component code
}
Blazor WebAssembly
For Blazor WebAssembly, you need to handle authentication and authorization differently since it runs on the client:
// In Program.cs of a Blazor WebAssembly project
builder.Services.AddAuthorizationCore(options =>
{
options.AddPolicy("AdminPolicy", policy =>
policy.RequireClaim("role", "Admin"));
});
Always remember that client-side authorization is not secure by itself. It should be used for UI concerns only, with actual security enforced on the server.
Best Practices for Authorization in .NET
- Defense in depth: Don't rely solely on UI hiding or client-side authorization
- Least privilege: Grant only the minimum permissions necessary
- Fail secure: Default to denying access unless explicitly granted
- Server validation: Always validate authorization rules on the server
- Audit logging: Log authorization decisions, especially failed attempts
- Regular review: Periodically review your authorization rules and access controls
Real-World Example: E-Commerce Application
Let's put together a more comprehensive example of authorization in an e-commerce application:
// Authorization policies
services.AddAuthorization(options =>
{
// Basic user access
options.AddPolicy("RegisteredUsers", policy =>
policy.RequireAuthenticatedUser());
// Customer support can view orders but not modify them
options.AddPolicy("CustomerSupport", policy =>
policy.RequireRole("Support"));
// Store managers can process orders and give refunds
options.AddPolicy("OrderProcessing", policy =>
policy.RequireRole("Manager", "Admin"));
// Only admins can access financial reports
options.AddPolicy("FinancialReports", policy =>
policy.RequireRole("Admin"));
// Custom policy for refunds based on amount
options.AddPolicy("RefundPolicy", policy =>
policy.Requirements.Add(new RefundAuthorizationRequirement()));
});
// Register custom handlers
services.AddTransient<IAuthorizationHandler, RefundAuthorizationHandler>();
// Order controller with different authorization levels
public class OrderController : Controller
{
private readonly IAuthorizationService _authorizationService;
public OrderController(IAuthorizationService authorizationService)
{
_authorizationService = authorizationService;
}
[Authorize(Policy = "RegisteredUsers")]
public IActionResult MyOrders()
{
// All logged-in users can see their own orders
return View();
}
[Authorize(Policy = "CustomerSupport")]
public IActionResult ViewOrder(int id)
{
// Support staff can view any order
return View();
}
[Authorize(Policy = "OrderProcessing")]
public IActionResult ProcessOrder(int id)
{
// Only managers and admins can process orders
return View();
}
public async Task<IActionResult> IssueRefund(int orderId, decimal amount)
{
var order = await _orderService.GetOrderAsync(orderId);
// Check if user has permission to issue this specific refund
var authResult = await _authorizationService.AuthorizeAsync(
User,
new RefundRequest { Order = order, Amount = amount },
"RefundPolicy");
if (!authResult.Succeeded)
{
return new ForbidResult();
}
// Process the refund
await _paymentService.RefundAsync(order, amount);
return RedirectToAction("OrderDetails", new { id = orderId });
}
}
Summary
Authorization in .NET provides a robust framework for controlling access to your application's resources. We've covered:
- Basic role-based authorization
- Claims-based authorization for more granular control
- Policy-based authorization for complex rules
- Resource-based authorization for per-object security
- Authorization in different .NET project types (MVC, Razor Pages, Blazor)
Remember that effective authorization is a critical part of your application's security strategy. It should be designed carefully, implemented thoroughly, and tested rigorously.
Additional Resources
- Official ASP.NET Core Authorization Documentation
- ASP.NET Core Identity
- Claims-Based Authorization
- Policy-Based Authorization
Exercises
- Create a simple web application with at least three different roles and appropriate authorization policies.
- Implement a custom authorization policy that allows access only during business hours (9 AM to 5 PM).
- Build a document management system where users can only edit their own documents, but administrators can edit any document.
- Extend the e-commerce example by adding an age verification policy for purchasing age-restricted products.
- Implement a multi-tenant application where users from one organization cannot access resources from another organization.
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)