Currently, I'm working on a project management website. There is a SuperAdmin (role) in the system, who can create new projects. The SuperAdmin can assign users to these projects with different roles, like admin or observer. A user can take different roles for different projects or even no role at all.
For example:
- User A is Admin for Project A
- User A is Observer for Project B (read access only)
- User A has no role Project C (can't access the project)
- User B is Admin for Project B
- User B is Observer for Project C (read access only)
The Identity does not seems like to support this behaviour. The claim-based authorization seems to support static claims, but not dynamic ones (accessing a project with an id). I can create my own UserProjectRoles class which associates the user, projects and the roles and use that in the authorization handler (policy-based authorization), but I was wondering if this is the right solution. How would you solve this task?
Edit:
I ended up doing the following: I created a new UserProjectClaims table, which connencts the Users and a Projects and contains the role type as an enum (I will move the role/claim type to it's own table, this was just for the sake of testing).
public class UserProjectClaim
{
public int ProjectId { get; set; }
public Project Project { get; set; }
public int UserId { get; set; }
public AppUser User { get; set; }
public UserProjectClaimEnum ClaimType { get; set; }
}
Then I used this table in the AuthorizationHandler as seen in the Policy-based authorization tutorial:
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, ProjectAdminRequirement requirement)
{
if (context.Resource is AuthorizationFilterContext authContext)
{
var projectId = Int32.Parse(authContext.HttpContext.Request.Query["projectId"]);
var userid = (await _userManager.GetUserAsync(context.User)).Id;
var claim = _dbContext.AppUsers.Include(i=>i.UserProjectClaims).Single(u=>u.Id==userid).UserProjectClaims.SingleOrDefault(p => p.ProjectId == projectId);
if (claim != null && claim.ClaimType == Data.Enums.UserProjectClaimEnum.ProjectAdmin)
{
context.Succeed(requirement);
}
else
{
context.Fail();
}
}
}
And it's working as expected.