Search code examples
microsoft-graph-apiazure-identity

ChainedTokenCredential results in a wrong token when requested from MS Graph in Azure


Trying to access MS Graph as web app running on App Service, according to instructions found here:

Tutorial: Access Microsoft Graph from a secured app as the app

Everything works fine when run locally. The same code however returns a faulty access token when run in Azure.

This code to set up my GraphServiceClient runs fine locally:

    var credential = new ChainedTokenCredential(
        new ManagedIdentityCredential(),
        new EnvironmentCredential());

    var token = credential.GetToken(
        new Azure.Core.TokenRequestContext(
            new[] { "https://graph.microsoft.com/.default" })

    var accessToken = token.Token;
    var graphServiceClient = new GraphServiceClient(
        new DelegateAuthenticationProvider((request) => {
        {
            request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("bearer", accessToken);
            return Task.CompletedTask;
        }));

When run in Azure the accessToken retrieved this way is incorrect. These are the differences:

  • appid: the correct appid of my deployment slot locally; incorrect in Azure.
    I can't find the returned Guid anywhere in my application manifest.
  • appidacr: "1" locally; "2" in Azure.
    Which is strange because my app has a system-assigned service principal but no certificate.
  • app_displayname: display name of my deployment slot locally (MyApp - Dev); deployment slot name in Azure (myapp/slots/dev).
  • roles: correct locally ("AppRoleAssignment.ReadWrite.All"); missing in Azure.
    Since the app does not seem to be correctly identified in the first place I guess it is expected that its application permissions are not found. According to me the application permission is configured correctly though, since it works locally.

Obviously, the next call with this GraphServiceClient returns Status Code: Forbidden since there are no roles in the token.

Microsoft.Graph.ServiceException: Code: Authorization_RequestDenied

I already tried:

  • Updated Microsoft.Web and Azure.Identity to the latest version (2.5.0 and 1.8.2 resp.)
  • Copied my local environment variables to Azure, in the hope they would be picked up by EnvironmentCredential. (AZURE_CLIENT_ID, AZURE_CLIENT_SECRET, and AZURE_TENANT_ID)
  • Replaced new ChainedTokenCredential(...) with new DefaultAzureCredential()
  • Replaced the 'default' scope URL ("https://graph.microsoft.com/.default") with a specific one ("https://graph.microsoft.com/AppRoleAssignment.ReadWrite.All").

All with the same result.

I have no clue, can only guess Azure.Identity does not work well with deployment slots? Which would be strange since they all have their own service principal.


Solution

  • As it appears, when assigning API Permissions to a web app, the Application ID is used and not the managed principal (?)

    That explains why this code works locally but not on Azure.

    var credential = new ChainedTokenCredential(
                new ManagedIdentityCredential(),
                new EnvironmentCredential());
    

    Locally there is no managed identity, so the chained token credential will only take credentials stored in environment variables into account. Credentials are stored in environment variables for developing locally

    On Azure with a managed identity on the web app, the chained token credential will use that first, and fail when requesting an access token since the API permission was set on the Application ID, not the managed identity.

    The solution in my case was to store credentials (Client ID, Client Secret) in environment variables both locally and on Azure, and only use environment variables to request the access token.

    var credential = new EnvironmentCredential();