The Short Version:
I have a ASP.net core Razorpages app Authenticating against Windows Users via .addNegotiate()
. Roles are then assigned based on AD Groups via a claims transformer, lets call one of the added Roles "Admin". And Additional policy "SiteAuth" has been set up based on these roles.
Authorizing against the "SiteAuth" policy a razor page using [Authorize(Policy = "SiteAuth")]
works, however [Authorize(Roles = "Admin")]
does not work and redirects to the Unauthorised page. Simplifying to [Authorize]
does work.
Further more if (User.HasClaim(claim => claim.Value == "Admin"))
also works as expected.
I've observed the claims transform being hit via a break point when hitting the page showing that the user should be authorized and the Claim has been added.
What have I missed or done wrong that [Authorize(Roles = "Admin")]
does not Authorize as expected?
Longer Tech Details Version:
Program.cs - relevant parts:
builder.Services.AddAuthentication(NegotiateDefaults.AuthenticationScheme)
.AddNegotiate();
builder.Services.AddAuthorization(options =>
{
// By default, all incoming requests will be authorized according to the default policy.
options.FallbackPolicy = options.DefaultPolicy;
options.AddPolicy("SiteAuth", policy => policy.RequireAssertion(
context => context.User.HasClaim(claim => claim.Type == ClaimTypes.Role && (new string[] { "SuperAdmin", "Admin", "User" }).Contains(claim.Value))));
});
builder.Services.AddControllers();
builder.Services.AddRazorPages().AddRazorPagesOptions(o =>
{
o.Conventions.ConfigureFilter(new IgnoreAntiforgeryTokenAttribute());
o.Conventions.Add(new ActionRouteConvention());
});
builder.Services.AddMemoryCache();
builder.Services.AddSession();
builder.Services.AddHttpContextAccessor();
builder.Services.AddScoped<IClaimsTransformation, ClaimsTransformer>();
/**** Other Services Added/Configured ***/
var app = builder.Build();
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
//app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseSession();
app.UseAuthentication();
app.UseStatusCodePagesWithReExecute("/errors/{0}");
app.UseAuthorization();
app.MapControllers();
app.MapRazorPages();
app.MapGet("/debug-claims", (ClaimsPrincipal user) =>
{
return Results.Ok(user.Claims.Select(c => new { c.Type, c.Value }));
}).RequireAuthorization();
app.Run();
ClaimsTransformer
public class ClaimsTransformer : IClaimsTransformation
{
private readonly SiteSettings _siteSettings;
private readonly IHttpContextAccessor _context;
private readonly ILogger<ClaimsTransformer> _logger;
public ClaimsTransformer(IHttpContextAccessor httpContextAccessor, SiteSettings siteSettings, ILogger<ClaimsTransformer> logger)
{
_siteSettings= siteSettings;
_context= httpContextAccessor;
_logger = logger;
}
public Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal)
{
var wi = (WindowsIdentity)principal.Identity;
var ci = (ClaimsIdentity)principal.Identity;
var groups = wi.Groups;
if (groups != null)
{
foreach (var group in groups) //-- Getting all the AD groups that user belongs to---
{
try
{
string groupName = string.Empty;
if(_siteSettings.AdminGroups.TryGetValue(group.Translate(typeof(NTAccount)).ToString(), out groupName))
{
if(!principal.HasClaim(c => c.Value == groupName))
{
//var claim = new Claim("RAAuth", groupName);
//wi.AddClaim(claim);
_logger.LogInformation($"Adding role {groupName} to claims.");
var claim = new Claim(ClaimTypes.Role, groupName);
//wi.AddClaim(claim);
ci.AddClaim(claim);
//claim = new Claim(wi.RoleClaimType, group.Value);
//wi.AddClaim(claim);
}
}
}
catch (Exception ex)
{
throw ex;
}
}
}
return Task.FromResult(principal);
}
}
Debug Info
Dumping the claims to screen gives:
{
"type": "http://schemas.microsoft.com/ws/2008/06/identity/claims/role",
"value": "Admin"
},
{
"type": "http://schemas.microsoft.com/ws/2008/06/identity/claims/role",
"value": "SuperAdmin"
},
Which strongly suggests the roles have been applied.
The role name was copy and pasted to the [Authorize(Roles = "Admin")]
to eliminate typos
Hopefully I've messed something simple here.
It is because when using windows authentication the "RoleClaim Name" is different. Just use like following:
...
var ci = (ClaimsIdentity)principal.Identity;
...
var claim = new Claim(ci.RoleClaimType, groupName);
...
Then the [Authorize(Roles = "Admin")]
will work.
To clarify:
ClaimTypes.Role : "http://schemas.microsoft.com/ws/2008/06/identity/claims/role"
ci.RoleClaimType: "http://schemas.microsoft.com/ws/2008/06/identity/claims/groupsid"