Reputation: 307
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.
Upvotes: 5
Views: 9937
Reputation: 35
I was able to get authorization working via policy setup in the Startup.cs
services.AddAuthorization(options =>
{
options.FallbackPolicy = options.DefaultPolicy;
options.AddPolicy("AdminOnly", policy => policy.RequireClaim(System.Security.Claims.ClaimTypes.Role, "Admin"));
});
Upvotes: 1
Reputation: 143
I used something similar to give custom role-claims to the user depending on their Active Directory groups.
public class ClaimsTransformer : IClaimsTransformation
{
private readonly IConfiguration _configuration;
public ClaimsTransformer(IConfiguration configuration)
{
_configuration = configuration;
}
public Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal)
{
var claimsIdentity = (ClaimsIdentity)principal.Identity;
// AppRole has two string props, Displayname and AdGroup. Get Adgroup from appsettings.json.
AppRole customRole = new AppRole()
{
DisplayName =_configuration.GetSection("Roles")
.GetSection("CustomRole")
.GetSection("DisplayName").Value,
AdGroup = _configuration.GetSection("Roles")
.GetSection("CustomRole")
.GetSection("AdGroup").Value
};
if (principal.IsInRole(customRole.AdGroup))
{
Claim customRoleClaim = new Claim(claimsIdentity.RoleClaimType, "CustomRole");
claimsIdentity.AddClaim(customRoleClaim);
}
return Task.FromResult(principal);
}
}
To get the Claimstransformer to work with the Authorize attribute, use this in Startup.cs:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
...
app.UseAuthorization();
app.UseAuthentication();
...
}
I used it for Blazor, so it can be used like this in the Blazor Component.
To authorize the whole component:
@attribute [Authorize(Roles = "CustomRole")]
or to authorize parts of the component:
<AuthorizeView Roles="CustomRole">
<Authorized>You are authorized</Authorized>
</AuthorizeView>
Upvotes: 3
Reputation: 307
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();
}
}
}
Upvotes: 3