Azure Function using "Easy Auth" for AuthN/Z is not returning role/group claims for endpoint authentication

Jason Olsan 21 Reputation points
2022-05-13T20:02:24.51+00:00

I'm attempting to build an SSO prototype using an Azure Function web API and a react-based SPA connected to Azure AD. The goal is to use "Easy Auth" (aka Azure Function integrated authentication) for my authentication on the Azure Function (https://learn.microsoft.com/en-us/azure/app-service/overview-authentication-authorization) with Microsoft Identity Platform as my provider.

First off, I created a React SPA using the following tutorial: https://learn.microsoft.com/en-us/azure/active-directory/develop/tutorial-v2-react
This seemed to authenticate just fine and I was able to consume the sample Graph API call.

However, once I attempted to then add the Azure Function to the mix, I ran into a problem. I used the POST call for "Client-directed sign-in" (https://learn.microsoft.com/en-us/azure/app-service/configure-authentication-customize-sign-in-out#client-directed-sign-in) to submit my AAD access token, but it failed.

I created a new button in the page that calls the following function:

export async function callExampleService(idToken, accessToken) {  
    const headers = new Headers();  

    headers.append("Content-Type", "application/json");  

    const options = {  
        method: 'POST',  
        headers: headers,  
        body: JSON.stringify({ access_token: `${accessToken}` })  
    };  

    return fetch(exampleDataServiceConfig.exampleDataServiceBase.concat(exampleDataServiceConfig.postAuth), options)  
        .then(response => response.json())  
        .catch(error => console.log(error));  
}  

I did find that my authentication seemed to work if I submitted the id token as the access token using the following change to the above code:

const options = {  
    method: 'POST',  
    headers: headers,  
    body: JSON.stringify({ access_token: `${idToken}` })  
};  

===

I was able to successfully authenticate on each endpoint in my test API without issue. However, I ran into an issue when integrating authorization on the endpoints.

According to the documentation (https://learn.microsoft.com/en-us/azure/app-service/configure-authentication-user-identities#access-user-claims-in-app-code), I should use HttpRequest.HttpContext.User as my ClaimsPrincipal for identity and authorization logic in an Azure Function.

I created three app roles (Test.View, Test.Edit, and Test.Admin) within the App Registration to validate authorization. The user is only a member of Test.View.

When I added logging to the ClaimsPrincipal for debugging, the object returned the following:

ClaimsPrincipal.Identity is not null
ClaimsPrincipal.Identity.Name is empty
ClaimsPrincipal.Identity.IsAuthenticated() is true
ClaimsPrincipal.Identity.AuthenticationType is AuthenticationTypes.Federation
ClaimsPrincipal.IsInRole("Test.View") is false
ClaimsPrincipal.IsInRole("Test.Edit") is false
ClaimsPrincipal.IsInRole("Test.Admin") is false

I iterated over the claims and returned the following:

Type: stable_sid, Value: sid:<id>
Type: exp, Value: <date>
Type: http://schemas.microsoft.com/identity/claims/identityprovider, Value: aad
Type: ver, Value: 3
Type: iat, Value: <date>
Type: nbf, Value: <same date as iat>
Type: http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier, Value: sid:<user id returned from AAD auth>
Type: iss, Value: <function url>

No claims seem to match any sort of role, as far as I can tell. I am also not sure how to find the "sid:" references in Azure AD.

The doc also mentions that the raw version of this information should be returned via the request headers, so I looked at them as well. Here are the "upper case" headers, which I believe are passed from the auth container:

X-ARR-LOG-ID | <guid>
X-ARR-SSL | 2048|256|<Microsoft cert info>
X-SITE-DEPLOYMENT-ID | <func name>
CLIENT-IP | <ip:port>
WAS-DEFAULT-HOSTNAME | <func url>
DISGUISED-HOST | <func url>
X-MS-CLIENT-PRINCIPAL-ID | sid:<user id returned from AAD auth>
X-MS-CLIENT-PRINCIPAL-IDP | aad
X-MS-CLIENT-PRINCIPAL | <JWT>
X-MS-CLIENT-AAD-ACCESS-TOKEN | <JWT ID token submitted by client>

The ClaimsPrincipal seems to be authenticated, but it doesn't seem to have a name or any roles/groups claims associated.

===

It should be noted that both the react client app and the azure function are both using the same Registered App credentials for authentication. I haven't found any details about whether that is the correct practice or that both need separate Registered Apps.

Here are a few related pieces of info to narrow down the problem:

  • http://localhost:3000 is in the CORS for the function.
  • http://localhost:3000 is in the Redirect URIs for the "Single Page Application" platform config of the app registration.
  • https://<function-name>.azurewebsites.net/.auth/login/aad/callback is in the Redirect URIs for the "Web" platform confict of the app registration.
  • <function-name> is just the placeholder for the actual name of the azure function, in case you think I actually used that for the name in the variables. ;)
  • Both "Access tokens" and "ID tokens" are not checked in the "Implicit grant and hybrid flows" since we're using msal.js 2.0
  • Test.Admin, Test.Edit, and Test.View are all allowed by Users/Groups and Applications in App Registration.
  • Under "Expose an API" in App Registration, the only scope is "impersonate_user" for the app.
  • Under "Toekn configuration" in App Registration, 2 optional claims are requested (for testing only): "groups" (ID, Access, SAML) and "preferred_username" (Access).
  • The access_token and id_token from client authentication are both valid on jwt.io, but access_token is validated via 512SHA and 256SHA. Using access_token as my access token returns a 401 validation error from the Azure Function endpoint due to token validation error. The id_token works when being sent as the access token. Weird.

Since the auth is done in a black box container, I'm not sure what steps I can take to get specifics on this issue. I have the following questions:

1) Is there a way to locally test the easy auth scenario? I currently have to add debug traces to the app insights logs in order to debug this thing. I'd prefer to grab a breakpoint and dig into the issue directly.
2) What might be causing this lack of ClaimsPrincipal detail?

Any help would be appreciated.

Azure Functions
Azure Functions
An Azure service that provides an event-driven serverless compute platform.
Microsoft Security | Microsoft Entra | Microsoft Entra ID
{count} votes

Your answer

Answers can be marked as 'Accepted' by the question author and 'Recommended' by moderators, which helps users know the answer solved the author's problem.