Previously in .NET Framework I used a custom RoleProvider
alongside Windows Authentication to deliver custom roles against the current principal as opposed to using Active Directory groups.
So, the goal is to be able to use the decorative [Authorize(Roles="")]
attribute where the roles are coming from a database and not active directory (or a combination of both would be fine).
To achieve this in core I believe I need to use IClaimsTransformation
to assign role claims as discussed here.
Here I'm just trying to add one role "Admin" however when I use [Authorize(Roles = "Admin")]
I get a 403 Unauthorised response.
Startup.cs
services.AddRazorPages();
services.AddAuthentication(IISDefaults.AuthenticationScheme);
services.AddSingleton<IClaimsTransformation, ClaimsTransformer>();
-------
app.UseAuthorization();
ClaimsTransformer.cs
public class ClaimsTransformer : IClaimsTransformation
{
public async Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal)
{
var identity = (ClaimsIdentity)principal.Identity;
var c = new Claim(identity.RoleClaimType, "Admin");
identity.AddClaim(c);
return await Task.FromResult(principal);
}
}
Annoyingly this works when I call User.IsInRole()
and I can see the group when I inspect the Claims so it is being added however it doesn't work with the Authorize attribute. Any advice would be appreciated.
Manged to solve this using ClaimsTransformer
alongside a custom TypeFilterAttribute
ClaimsTransformer.cs
public async Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal)
{
var identity = (WindowsIdentity)principal.Identity;
Guid userGuid;
SecurityIdentifier sid = identity.User;
using (DirectoryEntry userDirectoryEntry = new DirectoryEntry("LDAP://<SID=" + sid.Value + ">"))
{
userGuid = userDirectoryEntry.Guid;
}
UserAccount user = null;
if (userGuid != Guid.Empty)
user = await db.UserAccounts.Where(x => x.GUID == userGuid).SingleOrDefaultAsync();
if (user == null)
return principal;
if (user.Historic)
return principal;
var claims = new List<Claim>();
foreach (var role in user?.UserAccountGroups)
{
claims.Add(new Claim(ClaimTypes.GroupSid, role.Group.Name));
};
identity.AddClaims(claims);
return principal;
}
GroupsAttribute.cs
[AttributeUsage(AttributeTargets.All, Inherited = false, AllowMultiple = true)]
public class GroupsAttribute : TypeFilterAttribute
{
public GroupsAttribute(string groups) : base(typeof(ClaimRequirementFilter))
{
Arguments = new object[] { groups };
}
}
public class ClaimRequirementFilter : IAuthorizationFilter
{
readonly string _groups;
public ClaimRequirementFilter(string groups)
{
_groups = groups;
}
public void OnAuthorization(AuthorizationFilterContext context)
{
var groups = _groups.Split(',');
bool hasClaim = false;
foreach (var group in groups)
{
if (context.HttpContext.User.Claims.Any(c => c.Type == ClaimTypes.GroupSid && c.Value.Equals(group.Trim(), StringComparison.OrdinalIgnoreCase)))
hasClaim = true;
}
if (!hasClaim)
{
context.Result = new ForbidResult();
}
}
}