Open johnnyreilly opened 1 year ago
A similar problem exists when linking an App Service. I think it will be great if it could pass the AD token "X-MS-TOKEN-AAD-ID-TOKEN" like EasyAuth do, in this way we could use Microsoft.Identity.Web.
Agreed @davide-bergamini-sevenit - I suspect this is a general problem and affects all linked backends. So would expect container apps to have a similar issue.
I've written up my workaround in this post: https://johnnyreilly.com/azure-ad-claims-static-web-apps-azure-functions
Thanks @warrenandre for this assistance.
Apologies if I have missed something in your description, and with a caveat that I've already upgraded my project to use .net 7.0 but I managed to get the user claims/roles working as expected when I deserialize the payload of the request to /getroles
, rather than using the req.HttpContext.User property...
[Function("GetRoles")]
public async Task<HttpResponseData> GetRoles(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", "post")]
HttpRequestData req)
{
var payload = JsonConvert.DeserializeObject<UserPayload>(await req.ReadAsStringAsync());
var roles = new List<string>();
foreach (var claim in payload.claims)
{
if (claim.typ == ClaimTypes.Role)
{
roles.Add(claim.val);
}
}
var response = req.CreateResponse(HttpStatusCode.OK);
await response.WriteAsJsonAsync(new { roles = roles });
return response;
}
public class UserPayload
{
public string identityProvider { get; set; }
public string userId { get; set; }
public string userDetails { get; set; }
public string accessToken { get; set; }
public List<UserClaims> claims { get; set; } = new();
public class UserClaims
{
public string typ { get; set; }
public string val { get; set; }
}
}
If I then query the /.auth/me
endpoint of the deployed application, I see both the claim and the userRole set.
The role is also included in all subsequent requests in the x-ms-client-principal
header
{
"clientPrincipal": {
"identityProvider": "aad",
"userId": "...",
"userDetails": "...",
"userRoles": [
"anonymous",
"authenticated",
"test-role"
],
"claims": [
...
{
"typ": "http://schemas.microsoft.com/ws/2008/06/identity/claims/role",
"val": "test-role"
},
...
]
}
}
Is your test-role
role a custom claims that you've configured against your Azure AD App Registration? That's what we were / are missing.
Oh wait, you're manually adding a role first? I'm not quite following what your Function("GetRoles")
is intended to do?
Yes, I created an application role in the App registration, then assign it to the required user/group in the corresponding Enterprise Application.
Without the getRoles function reading the claims and returning the custom role it isn't included in the userRoles array
Sorry, missed a crucial detail as it coincided with how you had named your function. In your staticwebapp.config.json file, you need to specify the rolesSource property to point to the GetRoles function. This is then called by the platform when a user logs in.
"auth": {
"rolesSource": "/api/GetRoles",
"identityProviders": ...
}
Oh I see! (I think)
You're not manually calling /api/getroles
yourself, you're implementing that function and with that in place, *other" functions will now (behind the scenes) invoke this and as a consequence have the custom claims that you've configured against your Azure AD App Registration?
https://learn.microsoft.com/en-us/azure/static-web-apps/assign-roles-microsoft-graph
That's worth knowing! And also mighty peculiar!
yes, sorry for the confusion!
I'll try and test this out - thanks for sharing!
It's funny, you read the docs here: https://learn.microsoft.com/en-us/azure/static-web-apps/assign-roles-microsoft-graph#verify-custom-roles and it's not obvious how the approach you're suggesting would work. However that could totally be ashortcoming of the docs - will have to suck it and see
works for me too as @houlgap suggested. Thanks, I spent so much time with @johnnyreilly blog, but was happy to find a solution which does not involve all those Graph queries. Happy New Year!
We had mixed results using solution from @houlgap, then realized that sometimes, the "typ" of the claim was just "roles", not "http://schemas.microsoft.com/ws/2008/06/identity/claims/role". Unclear as to when or why this happens, but it happens frequently with the same browser from the same user.
Changed code to:
if (claim.typ == ClaimTypes.Role || claim.typ == "roles")
{
roles.Add(claim.val);
}
as our work-around.
@houlgap @steverhall Thank you for all sharing. But I found more mysterious problem.
In my PoC, the /api/getRoles is not called at all by the SWA platform. I can call that function directly through browser with the generated function-call url with the code parameter. But through the Function's Monitor, I can see there's no other calls except my manual ones.
Do you have thoughts on this issue?
I'm wondering if it's caused by that my backend function is deployed independently and linked to the SWA afterwards. Except this issue, all the interactions among AAD, SWA and AZ Function work well as excepted.
My frontend is a simple React app. And the backend is .Net/C# AZ Function.
Hi,
This function gets called by SWA during the authentication process. In order for it to be called, it must be an open API (no function key or authentication required).
Get Outlook for iOShttps://aka.ms/o0ukef
From: leevi-sa @.> Sent: Monday, July 31, 2023 7:38:14 AM To: Azure/static-web-apps @.> Cc: Mention @.>; Comment @.> Subject: Re: [Azure/static-web-apps] Azure AD Claims with Static Web Apps and Azure Functions (Authorization) (Issue #988)
@houlgaphttps://github.com/houlgap @steverhallhttps://github.com/steverhall Thank you for all sharing. But I found more mysterious problem.
In my PoC, the /api/getRoles is not called at all by the SWA platform. I can call that function directly through browser with the generate function-call url. But through the Function's Monitor, I can see there's no other calls except my manual ones.
Do you have thoughts on this issue?
I'm wondering if it's caused by that my backend function is deployed independently and linked to the SWA afterwards. Except this issue, all the interactions among AAD, SWA and AZ Function work well as excepted.
My frontend is a simple React app. And the backend is .Net/C# AZ Function.
— Reply to this email directly, view it on GitHubhttps://github.com/Azure/static-web-apps/issues/988#issuecomment-1658199868 or unsubscribehttps://github.com/notifications/unsubscribe-auth/ABVMVCHBWH52APHDTPCHSADXS6KKPBFKMF2HI4TJMJ2XIZLTSOBKK5TBNR2WLJDUOJ2WLJDOMFWWLO3UNBZGKYLEL5YGC4TUNFRWS4DBNZ2F6YLDORUXM2LUPGBKK5TBNR2WLJDUOJ2WLJDOMFWWLLTXMF2GG2C7MFRXI2LWNF2HTAVFOZQWY5LFUVUXG43VMWSG4YLNMWVXI2DSMVQWIX3UPFYGLLDTOVRGUZLDORPXI6LQMWWES43TOVSUG33NNVSW45FGORXXA2LDOOJIFJDUPFYGLKTSMVYG643JORXXE6NFOZQWY5LFVEZDKMZVG42TEOBRQKSHI6LQMWSWS43TOVS2K5TBNR2WLKRRGQ2DSMZRGUYDCMVHORZGSZ3HMVZKMY3SMVQXIZI. You are receiving this email because you were mentioned.
Triage notifications on the go with GitHub Mobile for iOShttps://apps.apple.com/app/apple-store/id1477376905?ct=notification-email&mt=8&pt=524675 or Androidhttps://play.google.com/store/apps/details?id=com.github.android&referrer=utm_campaign%3Dnotification-email%26utm_medium%3Demail%26utm_source%3Dgithub.
Hi @steverhall, thank you for the direction. Yes. It works now.
The mistake I made is that I did not change the default authLevel. VS uses AuthorizationLevel.Function
as the default value. It's working perfectly after I change it back to AuthorizationLevel.Anonymous
.
Thank you for providing this approach @johnnyreilly and others. I've got a few questions:
Edit: Questions got pretty much answered by reading the docs more carefully. Leaving the answers here though.
rolesSource
endpoint be added to the userRoles
claim? I assume so but it's not clear to me from reading the docs. Edit: Yes, it will be added as explained in the docs.rolesSource
? What's the input and what's the expected output? What happens if there is an error? How would I detect if calling this endpoint fails? Edit: Also explained in the docs, however, no idea how to check if it works other than checking /.auth/me or checking the request traces.To be honest, it's been a while since I did this and all the knowledge I have had been composed into this post:
https://johnnyreilly.com/azure-ad-claims-static-web-apps-azure-functions
Am understanding correctly that the backchannel principal header only contains claims of type role from the Web App /.auth/me
document? I wanted to use this for a multi-tenant app, so the tenantid
claim is the most important to me, and it's missing.
Describe the bug
Azure AD app role claims are not supplied to Azure Functions when linked with Azure Static Web Apps using the "bring your own functions" / linked backend approach. This impairs implementing authorization against endpoints.
To Reproduce Steps to reproduce the behavior:
/.auth/me
endpoint in the SWA - note the claims; they should include one of your custom app roles. egOurApp.Read
Which will then be accessed at the static web app's
/api/GetRoles
endpoint:At first look, this seems great; we have claims! But when we look again we realise that we have far less claims than we might have hoped for. Crucially, our custom claims / app roles like
OurApp.Read
are missing.Expected behavior
Where a Static Web App has a linked Function App, the Function App should receive a user's App Roles custom claims in calls to API endpoints, in the same way they do at the SWA's
./auth/me
endpoint. Not just UserRoles.Why is this important?
In a word: authorisation. App Roles custom claims are typically used to apply authorisation against applications. Without this in place people have to handroll an authorisation mechanism. The methods they come up with can be insecure and often do not scale.
Device info (if applicable):
N/A
Additional context
I'd be happy to demo this directly and share code. I'm working with Warren Joubert of Microsoft on a workaround for this and understand this to be a general problem that users are experiencing. It would be tremendous to get this remedied.
Writing this problem up here, with a workaround - ideally this shouldn't be needed