-
Notifications
You must be signed in to change notification settings - Fork 0
Authentication
Below is a guide on the steps required to create a new authorization policy and apply it to an endpoint
Creating a new authorization policy for use with endpoints is done by creating a new cs file in GirafAPI/Authorization
, below is the OwnDataRequirement
file:
using GirafAPI.Data;
using GirafAPI.Entities.Users;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
namespace GirafAPI.Authorization;
public class OwnDataRequirement : IAuthorizationRequirement;
public class OwnDataAuthorizationHandler : AuthorizationHandler<OwnDataRequirement>
{
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly UserManager<GirafUser> _userManager;
public OwnDataAuthorizationHandler(
IHttpContextAccessor httpContextAccessor,
UserManager<GirafUser> userManager)
{
_httpContextAccessor = httpContextAccessor;
_userManager = userManager;
}
protected override async Task HandleRequirementAsync(
AuthorizationHandlerContext context,
OwnDataRequirement requirement)
{
var userId = _userManager.GetUserId(_httpContextAccessor.HttpContext.User);
var user = await _userManager.FindByIdAsync(userId);
if (user == null)
{
context.Fail();
return;
}
var httpContext = _httpContextAccessor.HttpContext;
var userIdInUrl = httpContext.Request.RouteValues["userId"].ToString();
var targetUser = await _userManager.FindByIdAsync(userIdInUrl);
if (targetUser == null)
{
context.Succeed(requirement);
return;
}
if (userId == userIdInUrl)
{
context.Succeed(requirement);
return;
}
context.Fail();
}
}
In this example the userId claim is used from the context to fetch the user from the userManager. The fetched user's id is then checked against the userId route value in order to see if the user is accessing their own information. In other policies, it may be the OrgMember
or OrgAdmin
claims which are checked against an orgId
route value.
- Ensure the function is async if reading from the database
- Avoid accessing the request body, thus requiring buffering and adding performance overhead
-
orgId
anduserId
are put into the request route instead of the body for this reason
-
In order to make the policy available to be applied to endpoints and groups, it must be configured in the services. This is done in the GirafAPI/Extensions/ServiceExtensions.cs
file as can be seen below:
public static IServiceCollection ConfigureAuthorizationPolicies(this IServiceCollection services)
{
services.AddScoped<IAuthorizationHandler, OrgMemberAuthorizationHandler>();
services.AddScoped<IAuthorizationHandler, OrgAdminAuthorizationHandler>();
services.AddScoped<IAuthorizationHandler, OrgOwnerAuthorizationHandler>();
services.AddScoped<IAuthorizationHandler, OwnDataAuthorizationHandler>();
services.AddAuthorization(options =>
{
options.AddPolicy("OrganizationMember", policy =>
policy.Requirements.Add(new OrgMemberRequirement()));
options.AddPolicy("OrganizationAdmin", policy =>
policy.Requirements.Add(new OrgAdminRequirement()));
options.AddPolicy("OrganizationOwner", policy =>
policy.Requirements.Add(new OrgOwnerRequirement()));
options.AddPolicy("OwnData", policy =>
policy.Requirements.Add(new OwnDataRequirement()));
});
return services;
}
The name of the policy when it is applied to an endpoint is the string within .AddPolicy("OwnData"...
.
Here is the endpoint for deleting an organization, where a policy is applied that requires the user to be the owner of the organization.
group.MapDelete("/{orgId}", async (int orgId, GirafDbContext dbContext) =>
{
try
{
Organization? organization = await dbContext.Organizations.FindAsync(orgId);
if (organization is null)
{
return Results.NotFound();
}
await dbContext.Organizations.Where(o => o.Id == orgId).ExecuteDeleteAsync();
return Results.NoContent();
}
catch (Exception ex)
{
return Results.Problem(ex.Message, statusCode: StatusCodes.Status500InternalServerError);
}
})
.RequireAuthorization("OrganizationOwner")
The very last line is where the policy is applied to the endpoint, the name relates back to the string value provided in the ServiceExtensions.cs
file.
https://learn.microsoft.com/en-us/aspnet/core/fundamentals/minimal-apis/security?view=aspnetcore-8.0