azure-functions-core-tools
azure-functions-core-tools copied to clipboard
Incorrect results from `ClaimsPrincipal` when running locally
We had https://github.com/Azure/static-web-apps-cli/issues/390 reported with the Static Web Apps CLI, in which using a ClaimsPrincipal
in a C# Function doesn't contain the roles that you would expect and IsInRole
returns false when it shouldn't, resulting in a different experience with auth locally compared to when it's deployed.
Context
The SWA CLI provides a mock of EasyAuth, in which you can simulate a flow through and then a token is generated for you. This token is then handled by the proxy in the CLI and serialized before being injected into the x-ms-client-principal
header. The header can be parsed like so:
public static class StaticWebAppsAuth
{
private class ClientPrincipal
{
public string IdentityProvider { get; set; }
public string UserId { get; set; }
public string UserDetails { get; set; }
public IEnumerable<string> UserRoles { get; set; }
}
public static ClaimsPrincipal Parse(HttpRequest req)
{
var principal = new ClientPrincipal();
if (req.Headers.TryGetValue("x-ms-client-principal", out var header))
{
var data = header[0];
var decoded = Convert.FromBase64String(data);
var json = Encoding.UTF8.GetString(decoded);
principal = JsonSerializer.Deserialize<ClientPrincipal>(json, new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
}
principal.UserRoles = principal.UserRoles?.Except(new string[] { "anonymous" }, StringComparer.CurrentCultureIgnoreCase);
if (!principal.UserRoles?.Any() ?? true)
{
return new ClaimsPrincipal();
}
var identity = new ClaimsIdentity(principal.IdentityProvider);
identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, principal.UserId));
identity.AddClaim(new Claim(ClaimTypes.Name, principal.UserDetails));
identity.AddClaims(principal.UserRoles.Select(r => new Claim(ClaimTypes.Role, r)));
return new ClaimsPrincipal(identity);
}
}
But when deployed to Azure, the ClaimsPrincipal
is automatically populated with the claims information from that header and you are not required to do manual parsing. You can see a sample app here https://github.com/aaronpowell/swa-blazor-cli-bug, which is deployed to https://thankful-desert-0d90ef310.1.azurestaticapps.net/secured (yeah, it's an ugly dumping string of claims).
Given that the x-ms-claims-principal
header that is injected via the SWA CLI can be decoded it's expected that the ClaimsPrincipal
would contain the same information.
Is it possible to inject a header that the Functions runtime (locally) will be able to create a ClaimsPrincipal
correctly, or at least one that responds to the IsInRole
call correctly? (so, mapping the roles from the header payload correctly)
Aside - I've tried to debug this myself and see where the problem is coming from but I'm unable to find docs on how to do end-to-end debugging of Azure Functions locally. I tried to load symbols from SourceLink but didn't managed to get the debugging working competely (I'm unable to hit the point where it creates the ClaimsPrincipal
), and when my debugging chain consisted of SWA CLI
-> My Functions App
-> Core Tools
-> WebJobs Script Host
-> WebJobs SDK
-> WebJobs SDK Extensions
I gave up 🤣. I'm yet to find where the ClaimsPrincipal
is created.