Documentation: How to use EasyAuth with Azure AppService and Roles?
See original GitHub issueDocumentation related to component
How to use EasyAuth with Azure App Service with Roles. I may be reporting a bug here also - but I’m not sure.
Please check all that apply
- typo
- documentation doesn’t exist
- documentation needs clarification
- error(s) in the example
- needs an example
Description of the issue
Hello! I’ve been working on trying to make use of Easy Auth with a .NET 5 project running in an Azure App Service (Linux). According to the docs:
At this time, ASP.NET Core does not currently support populating the current user with the Authentication/Authorization feature. However, some 3rd party, open source middleware components do exist to help fill this gap.
The 3rd party component is: https://github.com/MaximRouiller/MaximeRouiller.Azure.AppService.EasyAuth
I initially started using @MaximRouiller’s shim but found an issue with the roles mapping which lead me to go with a slightly tweaked approach which I’ve blogged about: https://blog.johnnyreilly.com/2021/01/azure-easy-auth-and-roles-with-dotnet-and-core.html
@MaximRouiller gave me the great news that official support was being worked upon and was good enough to point me in the direction of @mattchenderson who was good enough to point me here suggesting that this was now a solved problem.
I happened upon the docs for the 1.2 release:
From version 1.2.0, the same code for your web app written with Microsoft.Identity.Web will work seamlessly with our without EasyAuth. Your web app can sign-in users and possibly call web APIs or Microsoft Graph. Indeed, Microsoft.Identity.Web now detects that the app is hosted in App Services, and uses that authentication.
This appears to be thanks to @jmprieur’s work on this PR: https://github.com/AzureAD/microsoft-identity-web/pull/700
It might be good to update the docs to clarify that Easy Auth now does work with Azure App Service for authentication. Now to the bug.
Roles with Azure App Service Easy Auth != Roles when directly using Microsoft.Identity.Web
Whilst the authentication seems to work, authorisation does not. So whilst my app knows who I am - the authorization is not working with relation to roles.
When directly using Microsoft.Identity.Web
(as we do locally) we see these claims:
[
// ...
{
"type": "http://schemas.microsoft.com/ws/2008/06/identity/claims/role",
"value": "Administrator"
},
{
"type": "http://schemas.microsoft.com/ws/2008/06/identity/claims/role",
"value": "Reader"
},
// ...
]
However, EasyAuth puts provides roles related claims with a different type:
[
// ...
{
"type": "roles",
"value": "Administrator"
},
{
"type": "roles",
"value": "Reader"
},
// ...
]
This means that roles related authorization does not work with Easy Auth:
[Authorize(Roles = "Reader")]
[HttpGet("api/reader")]
public string GetWithReader() =>
"this is a secure endpoint that users with the Reader role can access";
This is because .NET is looking for claims with a type
of "http://schemas.microsoft.com/ws/2008/06/identity/claims/role"
and not finding them with Easy Auth.
My feeling is that this may be a bug. Running directly against Microsoft.Identity.Web
has different behaviour to using Easy Auth. Ideally these would be the same.
Is there a workaround?
Whilst ideally this would just work… I wonder if there might be a workaround which we could use now. I’d submitted a PR to add a OnClaimsReceived
event to MaximeRouiller.Azure.AppService.EasyAuth
. If merged it would faciliate usage like this:
services.AddAuthentication("EasyAuth").AddEasyAuthAuthentication(options =>
options.Events.OnClaimsReceived = (claims) => {
var mappedRolesClaims = claims
.Where(claim => claim.Type == "roles")
.Select(claim => new Claim(ClaimTypes.Role, claim.Value))
.ToList();
return Task.FromResult(claims.Concat(mappedRolesClaims));
});
Is there a similar event available that we could hook into right now which would support this sort of mechanism? This might be a good short term measure. I’ve had a look at https://github.com/AzureAD/microsoft-identity-web/blob/master/src/Microsoft.Identity.Web/AppServicesAuth/AppServicesAuthenticationHandler.cs but nothing has leaped out at me so far.
Code
We’re using a .NET 5 project, running in an Azure App Service (Linux).
<PackageReference Include="Microsoft.Identity.Web" Version="1.4.1" />
In my Startup.cs
we’re using:
public void ConfigureServices(IServiceCollection services) {
//...
services.AddMicrosoftIdentityWebAppAuthentication(Configuration);
//...
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env) {
//...
app.UseAuthentication();
app.UseAuthorization();
//...
}
The relevant portion of the azuredeploy.json
that enables EasyAuth looks like this:
"siteConfig": {
"siteAuthSettings": {
"enabled": true,
"unauthenticatedClientAction": "RedirectToLoginPage",
"tokenStoreEnabled": "true",
"defaultProvider": "AzureActiveDirectory",
"clientId": "[parameters('clientId')]",
"issuer": "[concat('https://sts.windows.net/', parameters('tenantId'), '/')]"
},
"linuxFxVersion": "[parameters('linuxFxVersion')]",
"alwaysOn": false,
"ftpsState": "Disabled",
"http20Enabled": true,
"minTlsVersion": "1.2",
"remoteDebuggingEnabled": false,
"appSettings": [
//...
]
},
Issue Analytics
- State:
- Created 3 years ago
- Comments:15 (7 by maintainers)
Hey @jmprieur
The benefit we’ve identified is around having a standard policy that can be applied. A little context may help here. Our organisation is of a reasonable size and is planning to migrate its estate from running on our own data centers into Azure. A large percentage of those applications being migrated are internal facing. We want to minimise the risk of applications being moved into Azure and potentially exposing customer data through endpoints that aren’t correctly secured.
A way to achieve this is through enforcing EasyAuth with Azure AD by policy (and allowing exceptions for public facing apps).
cc @jamiemccrindle - please do add any detail / nuance that might better express the need here.
The solution is provided here
The relevant code for reference ``` // This is required to be instantiated before the OpenIdConnectOptions starts getting configured. // By default, the claims mapping will map claim names in the old format to accommodate older SAML applications. // ‘http://schemas.microsoft.com/ws/2008/06/identity/claims/role’ instead of ‘roles’ // This flag ensures that the ClaimsIdentity claims collection will be built from the claims in the token JwtSecurityTokenHandler.DefaultMapInboundClaims = false;