I'm building a ASP.NET Core (v3.1) module and I just managed to configure an OpenIdConnect Authentication. Now, I need to get all User's roles from an API in order to give or deny access to them, then I added multiple Claim values to the same Claim role "ClaimTypes.Role" in the User's Claims list through OnAuthorizationCodeReceived
Event like so:
OnAuthorizationCodeReceived = async (context) =>
{
// Uses the authentication code and gets the access and refresh token
var client = new HttpClient();
var response = await client.RequestAuthorizationCodeTokenAsync(new AuthorizationCodeTokenRequest()
{
Address = urlServer + "/connect/token",
ClientId = "hybrid",
Code = context.TokenEndpointRequest.Code,
RedirectUri = context.TokenEndpointRequest.RedirectUri,
}
if (response.IsError) throw new Exception(response.Error);
var identity = new ClaimsIdentity(context.Principal.Identity);
var listRoles = GenericProxies.RestGet<List<string>>(urlGetRoles, response.AccessToken); // GET request to API
listRoles.ForEach(role => identity.AddClaim(new Claim(ClaimTypes.Role, role)));
context.HttpContext.User = new ClaimsPrincipal(identity);
context.HandleCodeRedemption(response.AccessToken, response.IdentityToken);
}
While debugging, I noticed all roles are added the the User's Claims list after this line:
context.HttpContext.User = new ClaimsPrincipal(identity);
But, apparently, in my Home controller (that is where the user is being redirected to, after authenticated), when I access HttpContext.User, I can't seem to find any of the roles I added before except for "Admin" (which I'm guessing is a default ClaimTypes.Role value).
[Authorize]
public IActionResult Index()
{
if (User.IsInRole("SomeRole"))
{
return RedirectToAction("SomeAction", "SomeController");
}
else
{
return RedirectToAction("Forbidden", "Error");
}
}
Reading some other posts forums and topics, I found that this is probably a context persistence issue, which I tried to solve with this code in my Account controller:
public async Task Login(string returnUrl = "/")
{
await HttpContext.ChallengeAsync(
"OIDC",
new AuthenticationProperties
{
AllowRefresh = false,
IsPersistent = true,
RedirectUri = returnUrl
});
}
Some examples said that I could use context.Principal.AddIdentity(identity);
in order to persist the new Claims list, but then I got the following error:
InvalidOperationException: only a single identity supported
IdentityServer4.Hosting.IdentityServerAuthenticationService.AssertRequiredClaims(ClaimsPrincipal principal)
Summing up, I must find a way to persist the role claims I added to the User's Claims list but I got no success until now.
Update on that, if that's useful to anyone.
I deduced that the problem whas with context.HttpContext.User = new ClaimsPrincipal(identity);
, which I understood previously to be the part of the code that handled the persistence of the new claims.
Actually, I did noticed there was a context.Principal
attribute of the type ClaimsPrincipal
, and looked like it was the actual Current context, so I digged into it and tried to figure out a way to add elements to its "IEnumerable<Claim> Claims
" attribute which is readonly.
After a while, I found the following solution that worked just fine for me:
Instead of
var identity = new ClaimsIdentity(context.Principal.Identity);
var listRoles = GenericProxies.RestGet<List<string>>(urlGetRoles, response.AccessToken); // GET request to API
listRoles.ForEach(role => identity.AddClaim(new Claim(ClaimTypes.Role, role)));
I tried
var identity = context.Principal.Identity as ClaimsIdentity;
if(identity != null)
{
var listRoles = GenericProxies.RestGet<List<string>>(urlGetRoles, response.AccessToken); // GET request to API
foreach (var role in listRoles)
{
identity.AddClaim(new Claim(ClaimTypes.Role, role));
}
}