Search code examples
c#.net-coreopenid-connectclaims

How do I persist multiple ClaimTypes.Role values in User's Claims list?


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.


Solution

  • 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));
        }
    }