For anyone else that runs into this issue. The resolution was as follows:
I needed to include the following code in my ConfigureServices mehtod:
options.GetClaimsFromUserInfoEndpoint = true;
options.ClaimActions.MapAll();
options.ClaimActions.Add(new RoleClaimAction());
The first line brings the roles claim over, but the values are still in a string array, and do not get automatically parsed into the ClaimPrincipal as a claim.
The second line maps all of the JWT properties to claims. This could probably be refactored to only map the roles property if needed.
The third line takes the string array in the "roles" claim and splits them into individual "role" claims so that you can properly use the User.InRole() methods or set up custom auth policies based on user roles.
Here is the code for the custom RoleClaimAction class:
public class RoleClaimAction : ClaimAction
{
private const string RoleClaimType = "roles";
public RoleClaimAction() : base(RoleClaimType, ClaimValueTypes.String)
{
}
public override void Run(JsonElement userData, ClaimsIdentity identity, string issuer)
{
//Map array of roles to separate role claims
if (userData.TryGetProperty(RoleClaimType, out var roles))
{
if (roles.ValueKind == JsonValueKind.Array)
{
var enumerator = roles.EnumerateArray();
while (enumerator.MoveNext())
{
AddRoleClaim(identity, enumerator.Current.ToString(), issuer);
}
}else if(roles.ValueKind == JsonValueKind.String)
{
AddRoleClaim(identity, roles.ToString(), issuer);
}
};
}
private void AddRoleClaim(ClaimsIdentity identity, string role, string issuer)
{
identity.AddClaim(new Claim(ClaimTypes.Role, role, ClaimValueTypes.String, issuer));
}
}