I have an application with tables like this:
Users can be members of multiple teams, and can have different Roles within the teams. For example a User may be a TeamAdmin for TeamA but just a normal member of TeamB. Because of this, simple Role checks and Policies defined with static values won't work.
I'm looking for a way to authorize Controller Actions for Users based on their Team and their role in the Team, which will either be added as a Claim or with a separate RoleTeam table. Assuming a Controller action like this:
[HttpGet]
[Authorize(???)]
public IActionResult ManageTeam(Guid teamId)
{ }
I would need to verify that the User has the TeamAdmin Role for the Team in question. Is there a clean way to decorate it with an Authorize attribute that can access the teamId
parameter? Or will I have to wrap the guts of all of these Actions in if (User.IsTeamAdmin(teamId)
... statements?
Authorization in ASP.NET Core is controlled with AuthorizeAttribute and its various parameters. In its most basic form, applying the [Authorize] attribute to a controller, action, or Razor Page, limits access to that component to authenticated users. Now only authenticated users can access the Logout function.
MemberShip is not compatible in asp.net core, so you cannot use it. If you don't want to migrate the database schema, you can use Identity instead.
ASP.NET Core introduces the concept of policies that you can apply to your Authorize
attribute. works like filters, but without writing filters.
Each policy has one or more requirements that must all be met for the policy to pass. The Microsoft docs have a good example of setting up policies. In your case I'd do something like the following:
First, start with a "requirement"
public class TeamAccessRequirement : IAuthorizationRequirement
{
}
Then add a requirement handler
public class TeamAccessHandler : AuthorizationHandler<TeamAccessRequirement>
{
private readonly DbContext dbContext;
public TeamAccessHandler(DbContext dbContext)
{
// example of an injected service. Put what you need here.
this.dbContext = dbContext;
}
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, TeamAccessRequirement requirement)
{
// pattern matching is cool. if you can't do this, use context.Resource as AuthorizationFilterContext before and check for not null
if (context.Resource is AuthorizationFilterContext authContext)
{
// you can grab the team id, (but no model builder to help you, sorry)
var teamId = Guid.Parse(authContext.RouteData.Values["teamId"]);
// now you can do the code you would have done in the guts of the actions.
if (context.User.IsTeamAdmin(teamId))
{
context.Succeed(requirement);
}
else
{
context.Fail();
}
}
return Task.CompletedTask;
}
}
Then, you need to put this all together and enable it in the Startup.cs
under ConfigureServices
, like this:
services.AddAuthorization(options =>
{
options.AddPolicy("HasAdminTeamAccess", policy =>
policy.Requirements.Add(new TeamAccessRequirement()));
});
services.AddTransient<IAuthorizationHandler, TeamAccessHandler>();
And finally, the usage:
[HttpGet]
[Authorize(Policy = "HasAdminTeamAccess")]
public IActionResult ManageTeam(Guid teamId)
{ }
Now your actions remain nice and clean. From here you can fine tune the policy by adding functionality to the requirements that you can call from the handler, or do whatever you want.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With