C# Authorization
Introduction
Authorization is a fundamental aspect of web application security that determines what resources a user can access after they've been authenticated. While authentication verifies who a user is, authorization controls what they can do within your application.
In this tutorial, we'll explore various authorization techniques in C# web applications, focusing on ASP.NET Core's robust authorization framework. We'll cover everything from basic role-based authorization to more complex policy-based approaches, helping you secure your web applications effectively.
Authentication vs. Authorization
Before diving deeper, let's clarify the distinction between these two security concepts:
- Authentication: Verifies the identity of a user (Who are you?)
- Authorization: Determines what resources an authenticated user can access (What can you do?)
Basic Authorization Concepts
Role-based Authorization
Role-based authorization is the most straightforward approach, where access to resources is controlled based on user roles.
Implementing Role-based Authorization
First, let's set up a simple ASP.NET Core application with role-based authorization:
// In Startup.cs or Program.cs (depending on your .NET version)
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie();
services.AddAuthorization();
services.AddControllersWithViews();
}
Now, let's create a controller with role-based authorization:
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
[Authorize(Roles = "Admin")]
public class AdminController : Controller
{
[HttpGet]
public IActionResult Dashboard()
{
return View();
}
}
In this example, only users with the "Admin" role can access the Dashboard action.
Applying Multiple Roles
You can authorize users with any of multiple roles:
[Authorize(Roles = "Admin,Manager")]
public class ManagementController : Controller
{
public IActionResult Index()
{
return View();
}
}
Users with either the "Admin" or "Manager" role can access this controller.
Authorizing Individual Actions
You can apply authorization at the action level:
public class ProductController : Controller
{
public IActionResult Index()
{
// Anyone can view products
return View();
}
[Authorize(Roles = "Admin,Inventory")]
public IActionResult Create()
{
// Only Admin or Inventory roles can create products
return View();
}
}
Policy-based Authorization
Policy-based authorization provides more flexibility than role-based authorization, allowing you to create custom authorization requirements.
Creating Authorization Policies
Here's how to set up a simple policy:
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie();
services.AddAuthorization(options =>
{
options.AddPolicy("RequireAdminRole", policy =>
policy.RequireRole("Admin"));
options.AddPolicy("SeniorEmployeesOnly", policy =>
policy.RequireClaim("EmployeeLevel", "Senior", "Executive"));
});
services.AddControllersWithViews();
}
Using Policies in Controllers
Now you can use these policies in your controllers:
[Authorize(Policy = "RequireAdminRole")]
public class AdminController : Controller
{
// Only accessible to users with Admin role
}
[Authorize(Policy = "SeniorEmployeesOnly")]
public class SeniorManagementController : Controller
{
// Only accessible to users with EmployeeLevel claim set to Senior or Executive
}
Custom Authorization Requirements
For more complex scenarios, you can create custom authorization requirements:
1. Create a Requirement
public class MinimumAgeRequirement : IAuthorizationRequirement
{
public int MinimumAge { get; }
public MinimumAgeRequirement(int minimumAge)
{
MinimumAge = minimumAge;
}
}
2. Create a Handler
public class MinimumAgeHandler : AuthorizationHandler<MinimumAgeRequirement>
{
protected override Task HandleRequirementAsync(
AuthorizationHandlerContext context,
MinimumAgeRequirement requirement)
{
if (!context.User.HasClaim(c => c.Type == "DateOfBirth"))
{
return Task.CompletedTask;
}
var dateOfBirth = Convert.ToDateTime(
context.User.FindFirst(c => c.Type == "DateOfBirth").Value);
var userAge = DateTime.Today.Year - dateOfBirth.Year;
if (dateOfBirth > DateTime.Today.AddYears(-userAge))
{
userAge--;
}
if (userAge >= requirement.MinimumAge)
{
context.Succeed(requirement);
}
return Task.CompletedTask;
}
}
3. Register the Requirement and Handler
services.AddAuthorization(options =>
{
options.AddPolicy("AtLeast18", policy =>
policy.Requirements.Add(new MinimumAgeRequirement(18)));
});
services.AddSingleton<IAuthorizationHandler, MinimumAgeHandler>();
4. Use the Policy
[Authorize(Policy = "AtLeast18")]
public class AdultContentController : Controller
{
// Only users 18 or older can access this
}
Resource-based Authorization
Sometimes authorization depends not just on the user but also on the specific resource being accessed. For example, ensuring users can only edit their own data:
public class DocumentAuthorizationHandler :
AuthorizationHandler<OperationAuthorizationRequirement, Document>
{
protected override Task HandleRequirementAsync(
AuthorizationHandlerContext context,
OperationAuthorizationRequirement requirement,
Document resource)
{
if (requirement.Name == "Edit" &&
context.User.FindFirst("UserId")?.Value == resource.OwnerId)
{
context.Succeed(requirement);
}
return Task.CompletedTask;
}
}
Using it in a controller:
public class DocumentsController : Controller
{
private readonly IAuthorizationService _authorizationService;
public DocumentsController(IAuthorizationService authorizationService)
{
_authorizationService = authorizationService;
}
[HttpGet]
public async Task<IActionResult> Edit(int id)
{
var document = GetDocumentById(id); // Get document from database
var authResult = await _authorizationService.AuthorizeAsync(
User, document, "Edit");
if (!authResult.Succeeded)
{
return new ForbidResult();
}
// User is authorized to edit this document
return View(document);
}
}
Practical Example: Building a Multi-tenant Application
Let's combine these concepts to build a simple multi-tenant application where users can have different roles across different organizations:
public class MultiTenantAuthorizationHandler :
AuthorizationHandler<RoleRequirement, Organization>
{
protected override Task HandleRequirementAsync(
AuthorizationHandlerContext context,
RoleRequirement requirement,
Organization resource)
{
// Get the user's membership for this organization
var orgId = resource.Id.ToString();
var userOrgClaim = context.User.FindFirst(c =>
c.Type == "OrganizationRoles" &&
c.Value.StartsWith($"{orgId}:"));
if (userOrgClaim == null)
{
return Task.CompletedTask;
}
// Check if the user has the required role in this organization
var roles = userOrgClaim.Value.Split(':')[1].Split(',');
if (roles.Any(r => r == requirement.Role))
{
context.Succeed(requirement);
}
return Task.CompletedTask;
}
}
public class RoleRequirement : IAuthorizationRequirement
{
public string Role { get; }
public RoleRequirement(string role)
{
Role = role;
}
}
In a controller:
[HttpGet("organizations/{orgId}/dashboard")]
public async Task<IActionResult> OrganizationDashboard(int orgId)
{
var organization = GetOrganizationById(orgId);
var authResult = await _authorizationService.AuthorizeAsync(
User, organization, new RoleRequirement("Admin"));
if (!authResult.Succeeded)
{
return new ForbidResult();
}
return View(organization);
}
Best Practices for Authorization
- Fail securely: Default to denying access unless explicitly allowed
- Use multiple layers: Apply authorization at different levels (controller, action, resource)
- Keep it simple: Start with the simplest authorization scheme that meets your needs
- Separate concerns: Keep authentication and authorization logic separate
- Test thoroughly: Ensure authorization rules work correctly in all scenarios
- Avoid hardcoding policies: Use configuration for authorization rules when possible
- Log authorization failures: Track failed authorization attempts for security monitoring
Summary
We've covered the essential concepts of authorization in C# web applications:
- The difference between authentication and authorization
- Role-based authorization for simple access control
- Policy-based authorization for more flexible requirements
- Custom authorization requirements for complex scenarios
- Resource-based authorization for fine-grained access control
Authorization is a critical aspect of web application security. By implementing proper authorization controls, you can ensure that users only have access to the resources and actions they're permitted to use, protecting your application and its data.
Additional Resources
- Official ASP.NET Core Authorization Documentation
- Security Best Practices in ASP.NET Core
- Claims-based Authorization
Exercises
-
Create a simple ASP.NET Core web application with two roles: "User" and "Admin". Implement controllers that are accessible based on these roles.
-
Implement a policy that requires users to have verified their email address before accessing certain features.
-
Build a document management system where users can only edit documents they own or have been explicitly granted access to.
-
Implement a multi-tenant application where users can have different roles across different organizations.
-
Create a custom authorization requirement that only allows access during business hours.
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)