This was my approach:
Implement the MyUser Class
public class MyUser: IdentityUser
{
}
Use ASP.Net Core DbContext in a separate "security" DBContext (I prefer bounded contexts responible for their own functionality)
public class SecurityDbContext : IdentityDbContext<MyUser>
{
public SecurityDbContext(DbContextOptions<SecurityDbContext> options)
: base(options)
{ }
}
Create a claims transformer. This is used to add the claims from the store into the ClaimsIdentity (The User object in your HttpContext)
public class ClaimsTransformer : IClaimsTransformer
{
private readonly SecurityDbContext _context;
private readonly UserManager<MyUser> _userManager;
public ClaimsTransformer(SecurityDbContext context, UserManager<MyUser> userManager)
{
_context = context;
_userManager = userManager;
}
public Task<ClaimsPrincipal> TransformAsync(ClaimsTransformationContext context)
{
var identity = context.Principal.Identity as ClaimsIdentity;
if (identity == null) return Task.FromResult(context.Principal);
try
{
if (context.Context.Response.HasStarted)
return Task.FromResult(context.Principal);
var claims = AddClaims(identity);
identity.AddClaims(claims);
}
catch (InvalidOperationException)
{
}
catch (SqlException ex)
{
if (!ex.Message.Contains("Login failed for user") && !ex.Message.StartsWith("A network-related or instance-specific error"))
throw;
}
return Task.FromResult(context.Principal);
}
private IEnumerable<Claim> AddClaims(IIdentity identity)
{
var claims = new List<Claim>();
var existing = _userManager.Users.Include(u => u.Claims).SingleOrDefault(u => u.UserName == identity.Name);
if (existing == null) return claims;
claims.Add(new Claim(SupportedClaimTypes.ModuleName, Constants.ADGroupName));
claims.AddRange(existing.Claims.Select(c => c.ToClaim()));
return claims;
}
}
You will need to add a few things into your startup.cs class, I have only added the pertinent ones here:
public void ConfigureServices(IServiceCollection services)
{
services.AddIdentity<FreightRateUser, IdentityRole>(config =>
{
config.User.AllowedUserNameCharacters =
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._@\\";
config.User.RequireUniqueEmail = false;
config.Cookies.ApplicationCookie.LoginPath = "/Auth/Login";
})
.AddEntityFrameworkStores<SecurityDbContext>();
}
Don't forget the Configure method
public async void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
app.UseIdentity();
}
Finally you'll need some View to add the appropriate User/claims to the Identity tables, I have a controller with a couple of Actions (only showing Create here):
[AllowAnonymous]
public IActionResult CreateAccess()
{
var vm = new AccessRequestViewModel
{
Username = User.Identity.Name
};
return View(vm);
}
[HttpPost]
[ValidateAntiForgeryToken]
[AllowAnonymous]
public async Task<IActionResult> CreateAccess(AccessRequestViewModel viewModel)
{
if (User == null || !User.Identity.IsAuthenticated) return View("Index");
var newUser = new MyUser
{
UserName = viewModel.Username
};
var x = await _userManager.CreateAsync(newUser);
if (!x.Succeeded)
return View(ModelState);
var myClaims = new List<Claim>();
if (viewModel.CanManageSecurity)
myClaims.Add(new Claim(SupportedClaimTypes.Security, "SITE,LOCAL"));
if (viewModel.CanChangeExchangeRates)
myClaims.Add(new Claim(SupportedClaimTypes.ExchangeRates, "AUD,USD"));
if (viewModel.CanChangeRates)
myClaims.Add(new Claim(SupportedClaimTypes.Updates, "LOCAL"));
if (viewModel.CanManageMasterData)
myClaims.Add(new Claim(SupportedClaimTypes.Admin, "SITE,LOCAL"));
await _userManager.AddClaimsAsync(newUser, myClaims);
}
return View("Index");
}
When the user is saved and you display the authenticated page again (Index) the claims transformer will load the claims into the ClaimsIdentity and all should be good.
Please Note, this is for the current HttpContext user, with will want to do the create for other users in the Create, as you obviously don't want the current user giving themselves access.