Open johndowns opened 5 years ago
Thanks for bringing this to my attention. I will investigate this scenario.
So it turns out that EasyAuth is using an older version of System.IdentityModels.Tokens.Jwt that doesn't have the "roles" claim mapped to "http://schemas.microsoft.com/ws/2008/06/identity/claims/role", which is why ClaimsPrincipal.IsInRole doesn't detect it.
We have a potential release of EasyAuth that has updated System.IdentityModels.Tokens.Jwt, but this would likely be a breaking change, as some people may have already written code against the "roles" claim. We are working on a way to make this work without causing a breaking change.
Good to see there is an issue open against this matter as I got hung up for a good chunk of time assuming principal.IsInRole()
was working before I broke down and added a claims dump to log output given issue #3810 where i'm unable to use attach debugger..
The work around i'm using is to use both the out of box role check and a current function app ClaimsPrincipal claim set compatible one for my role checks. While this does lead to unnecessary code it should not be susceptible to breaking if the "roles" array in issued tokens eventually gets mapped to a set of ClaimTypes.Role claims in ClaimsPrincipal.
// see https://github.com/Azure/azure-functions-host/issues/3898
public static bool IsInRoleFuncApp(this ClaimsPrincipal principal, string roleName)
{
const string FunctionAppPrincipalRoleClaimType = "roles"; // vs ClaimTypes.Role == "http://schemas.microsoft.com/ws/2008/06/identity/claims/role"
foreach(Claim claim in principal.FindAll(FunctionAppPrincipalRoleClaimType)) { if (claim.Value == roleName) return true; }
return false;
}
[FunctionName("Interpret")]
public static async Task<IActionResult> Run( . . . )
{
. . .
log.LogInformation($"the current user is {principal.Identity.Name} with isAuthenticated = {principal.Identity.IsAuthenticated}");
//foreach (var claim in principal.Claims) { log.LogInformation($"current user has claim type = {claim.Type} with value = {claim.Value}"); }
if (principal.IsInRole("Sales")) log.LogInformation($"the current user {principal.Identity.Name} IsInRole(\"Sales\") check returned true");
if (principal.IsInRole("Finance")) log.LogInformation($"the current user {principal.Identity.Name} IsInRole(\"Finance\") check returned true");
if (principal.IsInRoleFuncApp("Sales")) log.LogInformation($"the current user {principal.Identity.Name} IsInRoleFuncApp(\"Sales\") check returned true");
if (principal.IsInRoleFuncApp("Finance")) log.LogInformation($"the current user {principal.Identity.Name} IsInRoleFuncApp(\"Finance\") check returned true");
Do you have an update on this @ConnorMcMahon ? Thanks!
Thanks for pinging me on this.
I don't think we can make a fix in the core Authentication/Authorization feature, as customers have developed code around the claims that it has always had present.
However, I can add some special logic in the Functions runtime to manually edit the ClaimsPrincipal to add a Claim for "roles" that has the same value as the claim for "http://schemas.microsoft.com/ws/2008/06/identity/claims/role".
I will avoid actually removing the claim for "http://schemas.microsoft.com/ws/2008/06/identity/claims/role", so I don't break workarounds like the one shown by @myusrn above.
With the code i used it wouldn't break if the role claims started being unwound into expected ClaimTypes.Role == "http://schemas.microsoft.com/ws/2008/06/identity/claims/role" entries and Principal.IsInRole("RoleName") started working. I would just have easily searchNremove dead code to strip out once this was the case.
@ConnorMcMahon, any update on the special logic in the Functions runtime? I still don't seem to be able to use IsInRole
properly with tokens from AAD.
I want to point out that this issue is not limited to Azure Functions. Any token returned by Azure AD authentication service that has an App Role specified will have the same issue.
Can you update us on the matter? It seems still an issue even in a simple asp.net app running on an App Service.
I just ran into this while re-deploying a Function app after a period of time. In the previous deployment, my application roles were presented in JWT claims as "http://schemas.microsoft.com/ws/2008/06/identity/claims/role", and IPrincipal.IsInRole worked as expected. After trying to redeploy with literally no code changes, it wasn't working any more because application roles now present as schemaless "roles" (plural), and I had to change usage of IPrincipal.IsInRole to ClaimsPrincipal.HasClaim ("roles", ...) - an unnecessary reference to a concrete class.
I understand not wanting to break people's recent changes, but along the way somebody already broke it and the way it's working now is just wrong, IMO.
At the very least, "roles" should be a member of ClaimTypes and the IsInRole method should treat "Role" and "Roles" the same...
When using the EasyAuth
ClaimsPrincipal
binding with an Azure AD token with an application role, theClaimsPrincipal.IsInRole()
method always returns false even when the token has the role included.Investigative information
Repro steps
roles
claim, which is a string array with a single item (SurveyAdmin
).Principal is NOT in the SurveryAdmin role.
since theIsInRole
method has returnedfalse
.Expected behavior
The
roles
claim contains the roles associated with that principal. It should be honoured when evaluating theIsInRole
method. (Also, on the main EasyAuth issue, @ConnorMcMahon indicated that this specific scenario should work.)Actual behavior
The
IsInRole
method returnsfalse
.Known workarounds
By manually inspecting the claims associated with the identity, we can perform our own version of the
IsInRole
logic.Related information