How to Implement Resource-Based Authorization in ASP.NET Core
By FoxLearn 2/3/2025 7:11:19 AM 73
While declarative authorization using the [Authorize] attribute works well in most cases, there are scenarios where it’s not enough, such as when you need to enforce access control on specific resources after they have been retrieved, like a user's profile information.
Attribute-Based vs. Resource-Based Authorization
ASP.NET Core’s built-in authorization middleware uses attributes like [Authorize]
to restrict access based on roles or claims. However, this middleware runs before any action method is executed, meaning it cannot access the resource on which the action will operate.
In scenarios like managing user profiles, you may need to ensure that only the user whose profile is being accessed or modified can perform actions like editing or deleting their profile. Since these decisions need to be made after retrieving the profile data, attribute-based authorization cannot be used in this case. This is where resource-based authorization comes into play.
What is Resource-Based Authorization?
Resource-based authorization ties the access control policies directly to the resource whether it’s a database record, file, or API endpoint. Instead of simply granting or denying access based on the user's identity, roles, or claims, resource-based authorization evaluates the resource itself, making decisions based on who can interact with the specific resource.
For example, in a user profile scenario, you may want to ensure that a user can only modify their own profile. The policy that grants or denies access would depend on the profile resource itself and the user attempting to interact with it.
Implementing Resource-Based Authorization in ASP.NET Core
Let’s walk through the steps of setting up resource-based authorization in an ASP.NET Core Web API project for a User Profile scenario.
Step 1: Create a New ASP.NET Core Web API Project
- Open Visual Studio 2022 and create a new project.
- Choose the ASP.NET Core Web API template.
- Name your project (e.g.,
UserProfileApi
) and select its location. - Choose .NET 9.0 as the framework version.
- Check Use Controllers (as we'll use controllers).
- Leave Authentication Type set to None and uncheck other optional features like Enable Docker.
- Click Create to generate your project.
Step 2: Define the Resource
In REST APIs, resources are typically represented by model objects. In our case, we will define a UserProfile
resource. Create a new class named UserProfile.cs
:
public class UserProfile { public int Id { get; set; } public string Username { get; set; } public string Bio { get; set; } internal readonly string Owner; // The username of the profile owner }
Step 3: Create a Repository for the Resource
To interact with the data, we need a repository. We will create an interface (IUserProfileRepository
) and an implementation (UserProfileRepository
) to access and retrieve user profiles.
IUserProfileRepository.cs:
public interface IUserProfileRepository { UserProfile GetUserProfile(int id); List<UserProfile> GetAllProfiles(); }
UserProfileRepository.cs:
public class UserProfileRepository : IUserProfileRepository { public UserProfile GetUserProfile(int id) { // Logic to retrieve the profile from a database throw new NotImplementedException(); } public List<UserProfile> GetAllProfiles() { // Logic to retrieve all profiles throw new NotImplementedException(); } }
Step 4: Register the Repository
In Program.cs
, register the UserProfileRepository
with the dependency injection container:
builder.Services.AddScoped<IUserProfileRepository, UserProfileRepository>();
Step 5: Create an Authorization Handler
The authorization handler checks if the user is authorized to access a specific resource. In this case, we will create a custom requirement and handler to determine if the user can modify or view their own profile.
UserProfileRequirement.cs:
public class UserProfileRequirement : IAuthorizationRequirement { }
UserProfileAuthorizationHandler.cs:
public class UserProfileAuthorizationHandler : AuthorizationHandler<UserProfileRequirement, UserProfile> { protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, UserProfileRequirement requirement, UserProfile resource) { if (context.User.Identity?.Name == resource.Owner) { context.Succeed(requirement); // User is allowed to edit or view their own profile } return Task.CompletedTask; } }
Step 6: Register the Authorization Handler
In Program.cs
, register the UserProfileAuthorizationHandler
:
builder.Services.AddSingleton<IAuthorizationHandler, UserProfileAuthorizationHandler>();
Step 7: Define Authorization Policies
Define a policy that specifies the requirements for accessing or modifying a user profile. This policy ensures that only the owner of the profile can make modifications.
In Program.cs
, add the following code to define the EditProfilePolicy
:
builder.Services.AddAuthorization(options => { options.AddPolicy("EditProfilePolicy", policy => policy.Requirements.Add(new UserProfileRequirement())); });
Step 8: Create the Controller
The controller exposes endpoints that allow users to access or modify their profiles. We will implement an EditProfile
method that uses the authorization service to check if the current user is authorized to modify the profile.
UserProfileController.cs:
[Route("api/[controller]")] [ApiController] public class UserProfileController : ControllerBase { private readonly IAuthorizationService _authorizationService; private readonly IUserProfileRepository _userProfileRepository; public UserProfileController(IAuthorizationService authorizationService, IUserProfileRepository userProfileRepository) { _authorizationService = authorizationService; _userProfileRepository = userProfileRepository; } [HttpPut("{id}")] public async Task<IActionResult> EditProfile(int id) { UserProfile userProfile = _userProfileRepository.GetUserProfile(id); if (userProfile == null) { return NotFound(); } var result = await _authorizationService.AuthorizeAsync(User, userProfile, "EditProfilePolicy"); if (result.Succeeded) { // Proceed with editing the profile logic (e.g., updating the profile) return Ok(userProfile); } return Forbid(); // Access denied if user is not the profile owner } }
In this code, the EditProfile
action checks whether the current user is authorized to edit the requested profile by verifying if they are the profile owner. If the user is authorized, the profile is edited; otherwise, access is denied.
- Options Pattern In ASP.NET Core
- Implementing Rate Limiting in .NET
- IExceptionFilter in .NET Core
- Repository Pattern in .NET Core
- CRUD with Dapper in ASP.NET Core
- How to Implement Mediator Pattern in .NET
- How to use AutoMapper in ASP.NET Core
- How to fix 'asp-controller and asp-action attributes not working in areas'