microsoftgraph/msgraph-sdk-dotnet-core

InvalidAuthenticationToken: Access token is empty error for Azure function in offline cloud environment

kfwalther opened this issue · 2 comments

We're receiving an "access token is empty" error in our Azure function when running in our offline cloud environment (different MS Graph endpoint, e.g. graph.abc.xyz), when making a request to MS Graph. Specifically, we're trying to subscribe to AAD user changes and send these change notifications to a webhook (separate Azure function). The relevant portion in our code where we create the GraphServiceClient and issue the request is reproduced below:

public async Task<string> CreateUserSubscriptionAsync(string tenantId, Subscription subscription)
{
    // Manually setup secret credential here in order to print token.
    var scopes = new[] { "https://graph.abc.xyz/.default" };
    var tokenOptions = new TokenCredentialOptions
    {
        AuthorityHost = new Uri("https://login.microsoftonline.abc.xyz")
    };
    var clientSecretCredential = new ClientSecretCredential(tenantId, options.ClientId, options.ClientSecret, tokenOptions);
    var accessToken = clientSecretCredential.GetToken(new Azure.Core.TokenRequestContext(scopes) { });
    // Print the token (token looks good when printed here)
    this.logger.LogInformation($"TOKEN: TenantId: {tenantId}, ExpireTime: {accessToken.ExpiresOn}, Token: {accessToken.Token}");

    // Create the GraphServiceClient
    var graphBaseUrl = "https://graph.abc.xyz/v1.0"
    var graphServiceClient = new GraphServiceClient(clientSecretCredential, scopes, graphBaseUrl);
    try
    {
        // ODataError thrown on this line for "InvalidAuthenticationToken: Access token is empty"
        var result = await graphServiceClient.Subscriptions.PostAsync(subscription);
        return "Creating Subscription succeeded: " + result;
    }
    catch (ODataError err)
    {
        return "Exception when trying to create subscription: " + err.Error.Code + ": " + err.Error.Message;
    }
}

Note: We've parameterized the endpoints in the code snippet above to use the correct domains/endpoints based on the current environment.

This function works fine when testing locally on my machine, and when deployed in our Azure test environment (endpoint: graph.microsoft.com); the subscription to user changes is successfully created. However, we get the "access token is empty" error when deployed to Azure in our target cloud environment (endpoint: graph.abc.xyz). We're printing the token in all environments, and it looks good. My hunch is that somehow the token is not being included in the Authorization header of the request (the PostAsync call) in the target environment. However, I'm not sure how to confirm this from the deployed Azure function, nor do I know what to look for in order to fix it. How is our token getting dropped?

It looks like, under the hood, the provider's allowed hosts are verified against a hard-coded list of endpoints. Is our code hitting this? Are there other areas in the code that are not cloud-agnostic that may be causing our issue?

Our project is using the Microsoft.Graph v5.9.0 Nuget package, and testing locally using .NET 6 / AzureFunctions v4 on Windows 11.

baywet commented

Thanks for reaching out. For reference this is an internal team and they access internal endpoints which can't be shared publicly and can't be added to the list of defaults here.
The solution here would be for you to override the defaults like so:

public async Task<string> CreateUserSubscriptionAsync(string tenantId, Subscription subscription)
{
    // Manually setup secret credential here in order to print token.
    var scopes = new[] { "https://graph.abc.xyz/.default" };
    var tokenOptions = new TokenCredentialOptions
    {
        AuthorityHost = new Uri("https://login.microsoftonline.abc.xyz")
    };
    var clientSecretCredential = new ClientSecretCredential(tenantId, options.ClientId, options.ClientSecret, tokenOptions);
    var accessToken = clientSecretCredential.GetToken(new Azure.Core.TokenRequestContext(scopes) { });
    // Print the token (token looks good when printed here)
    this.logger.LogInformation($"TOKEN: TenantId: {tenantId}, ExpireTime: {accessToken.ExpiresOn}, Token: {accessToken.Token}");

    // Create the GraphServiceClient
    var graphBaseUrl = "https://graph.abc.xyz/v1.0"
+    var authenticationProvider = new AzureIdentityAuthenticationProvider(clientSecretCredential, new string[] { "host.xyz"}, scopes: scopes);
-    var graphServiceClient = new GraphServiceClient(clientSecretCredential, scopes, graphBaseUrl);
+    var graphServiceClient = new GraphServiceClient(authenticationProvider, graphBaseUrl);
    try
    {
        // ODataError thrown on this line for "InvalidAuthenticationToken: Access token is empty"
        var result = await graphServiceClient.Subscriptions.PostAsync(subscription);
        return "Creating Subscription succeeded: " + result;
    }
    catch (ODataError err)
    {
        return "Exception when trying to create subscription: " + err.Error.Code + ": " + err.Error.Message;
    }
}

Thanks for the feedback - it looks like this resolved the "token is empty" error!