Azure / Azure/azure-functions-core-tools
Incorrect results from `ClaimsPrincipal` when running locally
- Dominant language
- C#
- Stars
- 1.5k
- Forks
- 498
- Avg merge
- 4d 20h
- Merged PRs (30d)
- 14
Description
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:
```csharp
public static class StaticWebAppsAuth
{
private class ClientPrincipal
{
public string IdentityProvider { get; set; }
public string UserId { get; set; }
public string UserDetails { get; set; }
public IEnumerable 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(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);
}
}
```
_[Reference](https://docs.microsoft.com/en-us/azure/static-web-apps/user-information?tabs=csharp#api-functions)_
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._
Contributor guide
Research direction
Reproduce the discrepancy across the Static Web Apps CLI, the local Functions app, Core Tools, and the WebJobs host using the x-ms-client-principal header described in the issue. Trace where the local ClaimsPrincipal is created and compare its claims and IsInRole behavior with the deployed app; done means local role claims match the decoded header and deployed behavior.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- azure, csharp
- Domain
- authentication, cli
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 25/100