I am new to IdentityServer and I have been struggling with this issue all day. So much so that I'm almost about to give up on this. I know this question has been asked over and over again and I have tried many different solutions but none seem to work. Hopefully you can help me push me in the right direction with this.
First I installed the IdentityServer4 templates by running dotnet new -i identityserver4.templates
and created a new project with the is4aspid template by running dotnet new is4aspid -o IdentityServer
.
After that i created a new IdentityServer database and ran the migrations. By that time I had a the default Identity database structure.
In Config.cs I changed MVC client
to the following:
new Client
{
ClientId = "mvc",
ClientName = "MVC Client",
AllowedGrantTypes = GrantTypes.Implicit,
ClientSecrets = { new Secret("47C2A9E1-6A76-3A19-F3C0-S37763QB36D9".Sha256()) },
RedirectUris = { "https://localhost:44307/signin-oidc" },
FrontChannelLogoutUri = "https://localhost:44307/signout-oidc",
PostLogoutRedirectUris = { "https://localhost:44307/signout-callback-oidc" },
AllowOfflineAccess = true,
AllowedScopes = { "openid", "profile", "api1", JwtClaimTypes.Role }
},
And changed the GetApis
method to this:
public static IEnumerable<ApiResource> GetApis()
{
return new ApiResource[]
{
new ApiResource("api1", "My API #1", new List<string>() { "role" })
};
}
There where of course no users in the database yet so i added a registration form and registered two dummy users, one with the username [email protected]
and one with the username [email protected]
.
To assign the roles to these user I created the following method in Startup.cs.
private async Task CreateUserRoles(IServiceProvider serviceProvider) {
var RoleManager = serviceProvider.GetRequiredService<RoleManager<IdentityRole>>();
var UserManager = serviceProvider.GetRequiredService<UserManager<ApplicationUser>>();
IdentityResult adminRoleResult;
IdentityResult subscriberRoleResult;
bool adminRoleExists = await RoleManager.RoleExistsAsync("Admin");
bool subscriberRoleExists = await RoleManager.RoleExistsAsync("Subscriber");
if (!adminRoleExists) {
adminRoleResult = await RoleManager.CreateAsync(new IdentityRole("Admin"));
}
if(!subscriberRoleExists) {
subscriberRoleResult = await RoleManager.CreateAsync(new IdentityRole("Subscriber"));
}
ApplicationUser userToMakeAdmin = await UserManager.FindByNameAsync("[email protected]");
await UserManager.AddToRoleAsync(userToMakeAdmin, "Admin");
ApplicationUser userToMakeSubscriber = await UserManager.FindByNameAsync("[email protected]");
await UserManager.AddToRoleAsync(userToMakeSubscriber, "Subscriber");
}
In the Configure
method of the same class I add the the parameter IServiceProvider services
and called the above method like so: CreateUserRoles(services).Wait();
. By this time my database did have two roles in it.
Next I created a new solution (within the same project) and in the Startup.cs file of that solution I added the following in the ConfigureServices
method.
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
services.AddAuthentication(options =>
{
options.DefaultScheme = "Cookies";
options.DefaultChallengeScheme = "oidc";
})
.AddCookie("Cookies")
.AddOpenIdConnect("oidc", options => {
options.SaveTokens = true;
options.ClientId = "mvc";
options.ClientSecret = "32D7A7W0-0ALN-2Q44-A1H4-A37990NN83BP";
options.RequireHttpsMetadata = false;
options.Authority = "http://localhost:5000/";
options.ClaimActions.MapJsonKey("role", "role");
});
After that I added app.UseAuthentication();
in the Configure
method of the same class.
Then I created a new page with the following if statements.
if(User.Identity.IsAuthenticated) {
<div>Yes, user is authenticated</div>
}
if(User.IsInRole("ADMIN")) {
<div>Yes, user is admin</div>
}
I logged in with [email protected]
but the second if statement returns False
. I inspected all the claims by looping over them like so.
@foreach (var claim in User.Claims) {
<dt>@claim.Type</dt>
<dd>@claim.Value</dd>
}
But there was no role claim to be found, only sid, sub, idp, preferred_username and name.
I tried to get the role in there so that the second if statement returns True but after trying and trying I have not yet been able to make it work. Can someone see what I have to do in order to make this work? I am an absolute beginner in IdentityServer4 and trying my best to understand it. Any help will be appreciated. Thanks in advance!
EDIT 1:
Thanks to this question and this question I got the feeling that I'm on the right track. I have made some modifications but I still can not get it to work. I just tried the following.
public class MyProfileService : IProfileService {
public MyProfileService() { }
public Task GetProfileDataAsync(ProfileDataRequestContext context) {
var roleClaims = context.Subject.FindAll(JwtClaimTypes.Role);
List<string> list = context.RequestedClaimTypes.ToList();
context.IssuedClaims.AddRange(roleClaims);
return Task.CompletedTask;
}
public Task IsActiveAsync(IsActiveContext context) {
return Task.CompletedTask;
}
}
Next I registered this class in the ConfigureServices method by adding the line services.AddTransient<IProfileService, MyProfileService>();
. After that I added a new a new line to the GetIdentityResources method, which looks like this now.
public static IEnumerable<IdentityResource> GetIdentityResources()
{
return new IdentityResource[]
{
new IdentityResources.OpenId(),
new IdentityResources.Profile(),
new IdentityResource("roles", new[] { "role" })
};
}
I also added the roles to my Mvc client like so: AllowedScopes = { "openid", "profile", "api1", "roles" }
.
Next I switched over to the other project and added the following lines in the .AddOpenIdConnect oidc.
options.ClaimActions.MapJsonKey("role", "role", "role");
options.TokenValidationParameters.RoleClaimType = "role";
But still, I cannot get it to work like I want it to. Anyone knows what I am missing?
I did it like this in .NET 5:
Add JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
before services.AddAuthentication
in Startup.cs
.
https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/issues/1349
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With