I am investigating using Windows Authentication with ASP.NET Core 5.0 MVC. I can use an Active Directory group name directly in the Authorize attribute, but that's proved problematic in the past.
I want to add claims to the list and use those values in the Authorize attributes. Various posts suggested that if I implemented an IClaimsTransformation, TransformAsync() would be called automatically but that doesn't appear to be the case. I never see the string In TransformAsync in the output in Visual Studio.
I am using VS 2019 and ASP.NET Core 5.0.
To recreate: I use a new ASP.NET Core Web App (Model-View-Controller) template:
- Target Framework: .NET 5.0 (Current)
- Authentication Type: Windows,
- Configure for HTTPS: yes
When I run it, I can see that I'm authenticated from the Hello DOMAIN\User message.
These are my additions:
ADClaimsTransformation.cs:
using Microsoft.AspNetCore.Authentication; using System.Diagnostics; using System.Security.Claims; using System.Threading.Tasks; namespace SOWinAuthN1.Services { public class ADClaimsTransformation : IClaimsTransformation { public Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal) { Debug.WriteLine("In TransformAsync"); var ci = (ClaimsIdentity)principal.Identity; var c = new Claim(ci.RoleClaimType, "Admin"); ci.AddClaim(c); return Task.FromResult(principal); } } } I can see from the Output window that this is never called.
In Startup.cs, I have:
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using SOWinAuthN1.Services; namespace SOWinAuthN1 { public class Startup { public Startup(IConfiguration configuration) { Configuration = configuration; } public IConfiguration Configuration { get; } // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { services.AddControllersWithViews(); services.AddTransient<IClaimsTransformation, ADClaimsTransformation>(); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } else { app.UseExceptionHandler("/Home/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.UseAuthorization(); app.UseEndpoints(endpoints => { endpoints.MapControllerRoute( name: "default", pattern: "{controller=Home}/{action=Index}/{id?}"); }); } } } SecureController.cs:
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; namespace SOWinAuthN1.Controllers { [Authorize(Roles = "Admin")] public class SecureController : Controller { public IActionResult Index() { return View(HttpContext.User.Claims); } } } Index.cshtml (located in .\Views\Secure):
@model IEnumerable<System.Security.Claims.Claim> @{ ViewData["Title"] = "Claims"; } <h1>Claims</h1> <table class="table"> <thead> <tr> <th>Subject</th> <th>Type</th> <th>Value</th> <th>Issuer</th> <th>OriginalIssuer</th> </tr> </thead> <tbody> @foreach (var item in Model) { <tr> <td>@item.Subject</td> <td>@item.Type</td> <td>@item.Value</td> <td>@item.Issuer</td> <td>@item.OriginalIssuer</td> </tr> } </tbody> </table> _Layout.cshtml - added to the navbar ul:
<li class="nav-item"> <a class="nav-link text-dark" asp-area="" asp-controller="Secure" asp-action="Index">Claims</a> </li> Am I barking up the wrong tree? Is there a better approach?
Ultimately, I'm expecting to have a transformer which maps a small number of AD group claims onto a larger number of operations, such that group A maps to operations 1, 2 and 4, whilst group B maps to 2, 4 and 5, say, with the plan to keep this mapping in config somewhere.