Reputation: 345
I've implemented role based auth several times pre 2.1. Followed the steps to scaffold the new 2.1 identities.
I extended the IdentityUser model to add additional fields, login works fine, new fields are present.
startup.cs configure services contains
services.AddDefaultIdentity<AppUser>()
.AddRoles<IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>();
I seeded the roles
IdentityRole role = new IdentityRole();
role.Name = "Administrator";
IdentityResult roleResult = roleManager.
CreateAsync(role).Result;
Then created a user and added to the role
AppUser user = new AppUser();
user.UserName = "Admin";
user.Email = "[email protected]";
user.Name = "Administrator";
user.LockoutEnabled = false;
user.EmailConfirmed = true;
IdentityResult result = userManager.CreateAsync(user, "password").Result;
if (result.Succeeded)
{
userManager.AddToRoleAsync(user, "Administrator").Wait();
}
Everything succeeded, and the database looks fine (AspNetUserRoles has links)
However, decorating a controller with a role will always return not authorized
[Authorize(Roles = "Administrator")]
But, a simple login check with [Authorize]
(no role) will work.
How might I fix this/what is the easiest way to incorporate the source code so I can step through/debug the [Authorize]
tags?
Upvotes: 16
Views: 12758
Reputation: 70184
I got roles working by calling JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
before services.AddAuthentication
in Startup.cs
.
I also added
services.AddScoped<IProfileService, ProfileService>();
and ProfileService.cs
that looks like this to map roles to claims:
public sealed class ProfileService : IProfileService
{
private readonly IUserClaimsPrincipalFactory<ApplicationUser> _userClaimsPrincipalFactory;
private readonly UserManager<ApplicationUser> _userMgr;
private readonly RoleManager<IdentityRole> _roleMgr;
public ProfileService(
UserManager<ApplicationUser> userMgr,
RoleManager<IdentityRole> roleMgr,
IUserClaimsPrincipalFactory<ApplicationUser> userClaimsPrincipalFactory)
{
_userMgr = userMgr;
_roleMgr = roleMgr;
_userClaimsPrincipalFactory = userClaimsPrincipalFactory;
}
public async Task GetProfileDataAsync(ProfileDataRequestContext context)
{
string sub = context.Subject.GetSubjectId();
ApplicationUser user = await _userMgr.FindByIdAsync(sub);
ClaimsPrincipal userClaims = await _userClaimsPrincipalFactory.CreateAsync(user);
List<Claim> claims = userClaims.Claims.ToList();
claims = claims.Where(claim => context.RequestedClaimTypes.Contains(claim.Type)).ToList();
if (_userMgr.SupportsUserRole)
{
IList<string> roles = await _userMgr.GetRolesAsync(user);
foreach (var roleName in roles)
{
claims.Add(new Claim(JwtClaimTypes.Role, roleName));
if (_roleMgr.SupportsRoleClaims)
{
IdentityRole role = await _roleMgr.FindByNameAsync(roleName);
if (role != null)
{
claims.AddRange(await _roleMgr.GetClaimsAsync(role));
}
}
}
}
context.IssuedClaims = claims;
}
public async Task IsActiveAsync(IsActiveContext context)
{
string sub = context.Subject.GetSubjectId();
ApplicationUser user = await _userMgr.FindByIdAsync(sub);
context.IsActive = user != null;
}
}
Source:
https://ffimnsr.medium.com/adding-identity-roles-to-identity-server-4-in-net-core-3-1-d42b64ff6675
Upvotes: 1
Reputation: 2105
In my case of ASP.NET Core 3 (preview) + Angular, solution was in AddAuthentication
services.AddDefaultIdentity<ApplicationUser>()
.AddRoles<IdentityRole>()
.AddRoleManager<RoleManager<IdentityRole>>()
.AddEntityFrameworkStores<ApplicationDbContext>();
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = IdentityConstants.ApplicationScheme;
options.DefaultChallengeScheme = IdentityConstants.ApplicationScheme;
options.DefaultSignInScheme = IdentityConstants.ExternalScheme;
});
Upvotes: 10
Reputation: 3898
I added role in claim. Then it works both for UI (HttpContext.User.IsInRole("Admin")
) and for authorize
attribute ([Authorize(Roles = "Admin")]
).
Startup.cs file:
public void ConfigureServices(IServiceCollection services)
{
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<WandContext>();
///..... other code
}
During authentication, I add role to my claim.
var invalidLoginAttempt = false;
var user = await _userManager.FindByNameAsync(loginModel.Email);
if (user != null)
{
var result = await _signInManager.CheckPasswordSignInAsync(user, loginModel.Password, lockoutOnFailure: true);
if (result.Succeeded)
{
var customClaims = new List<Claim>
{
new Claim(ClaimTypes.Role, Role.Admin)
};
var claimsIdentity = new ClaimsIdentity(customClaims, CookieAuthenticationDefaults.AuthenticationScheme);
var claimsPrincipal = new ClaimsPrincipal(claimsIdentity);
await _signInManager.Context.SignInAsync(IdentityConstants.ApplicationScheme,
claimsPrincipal, new AuthenticationProperties { IsPersistent = loginModel.RememberMe });
return LocalRedirect(returnUrl);
}
else if (result.IsLockedOut)
ModelState.AddModelError(string.Empty, "This account has been locked out, please try again later.");
else
invalidLoginAttempt = true;
}
else
invalidLoginAttempt = true;
Upvotes: 1
Reputation: 25380
However, decorating a controller with a role will always return not authorized
[Authorize(Roles = "Administrator")]
It's a known bug in the version of 2.1
. See issue here .
I follow the advice of using the old api suggested by HaoK and C-BERBER , and it now works flawlessly .
Here's my DbContext
:
public class ApplicationDbContext : IdentityDbContext<AppUser,IdentityRole,string>
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{
}
}
Configure the identity using the old-style api :
services.AddIdentity<AppUser, IdentityRole>()
.AddRoleManager<RoleManager<IdentityRole>>()
.AddDefaultUI()
.AddDefaultTokenProviders()
.AddEntityFrameworkStores<ApplicationDbContext>();
Lastly , logout and re-signin , it will work as expected now .
I guess you won't want to debug the AuthorizeAttribe
itself , since it is processed at compile-time . If you mean to debug the AuthorizeFilter
, you can follow the steps as below :
click Tools
-> Options
-> Debugging
General
, unselect the Enable Just My Code
in Visual StudioEnable Source Link Support
Symbols
, make sure that the Microsoft Symbol Servers is selectedAnd you can debug the source code now . However , due to the way that filter works , you need set a breakpoint before MVC . I just set a dummy middleware that will take place before the MVC router handler :
The screenshot of debugging AuthorizeFiler
:
Upvotes: 13