Search code examples
c#asp.net-coremicrosoft-graph-apibearer-token

How to fix calling GetAccessTokenForAppAsync to acquire token resulting in a NullReferenceException?


I'm working on figuring out how to use Microsoft Graph API in a ASP.NET Core 3.1 Razor Pages application. I found this guide and got most of it to work (along with retrieving a token) until I realized I need to get access to the API without a user.

At the moment, I am stuck because I am not able to retrieve a token using the ITokenAcquisition GetAccessTokenForAppAsync method. It keeps resulting in a NullReferenceException. I don't know if my startup setup is wrong or what, but I can't figure it out.

System.NullReferenceException: 'Object reference not set to an instance of an object.'

I'm aware of the Get access without a user guide which I understand and can get to work, but I specifically want to use GetAccessTokenForAppAsync method because it will manage refreshing tokens for me. Otherwise, I'd have to keep querying for a new token with every API call and constantly generating valid tokens seems like a bad idea.

Startup.cs ConfigureServices method:

public void ConfigureServices(IServiceCollection services)
{
    ...
    
    services.AddHttpClient();
    services.AddMicrosoftIdentityWebApiAuthentication(Configuration)
        .EnableTokenAcquisitionToCallDownstreamApi()
        // Use in-memory token cache
        // See https://github.com/AzureAD/microsoft-identity-web/wiki/token-cache-serialization
        .AddInMemoryTokenCaches();
        
    ...

}

Index.cshtml.cs. This is where I make my call to get the token:

public class IndexModel : PageModel
{
    private readonly IHttpClientFactory _clientFactory;
    private readonly ITokenAcquisition _tokenAcquisition;

    public IndexModel(IHttpClientFactory clientFactory, ITokenAcquisition tokenAcquisition)
    {
        _clientFactory = clientFactory;
        _tokenAcquisition = tokenAcquisition;
    }

    public async Task OnGet()
    {
        // results in NullReferenceException
        string token = await _tokenAcquisition.GetAccessTokenForAppAsync("https://graph.microsoft.com/.default", tenant:"tenantId");
    }
}

appSettings.json. The values are populated by user secrets json.

{
  "AzureAd": {
    "Instance": "",
    "ClientId": "",
    "TenantId": "",
    "CallbackPath": "",
    "ClientSecret": "",
    "TimeoutInMinutes": ""
  },
  "ConnectionStrings": {
    "DefaultConnection": ""
  },
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "AllowedHosts": "*"
}


Solution

  • First of all, I have successfully reproduced your issue, as you can see below:

    enter image description here

    You are getting this because of private readonly ITokenAcquisition _tokenAcquisition;

    Note: This is actually a service which helps you to acquire access token on behalf of application. You cannot consume this service as constructor variable.

    Solution:

    Instead of that you should use ITokenAcquisition service in the below way:

       public async Task OnGet()
            {
                var _tokenAcquisition = this.HttpContext.RequestServices
                     .GetRequiredService<ITokenAcquisition>() as ITokenAcquisition;
                string token = await _tokenAcquisition.GetAccessTokenForAppAsync("https://graph.microsoft.com/.default", tenant: "tenantId");
            }
    

    Configuration Under Startup.cs

    public void ConfigureServices(IServiceCollection services)
    {
        string[] initialScopes = Configuration.GetValue<string>("DownstreamApi:Scopes")?.Split(' ');
    
        services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
            .AddMicrosoftIdentityWebApp(Configuration.GetSection("AzureAd"))
            .EnableTokenAcquisitionToCallDownstreamApi(initialScopes)
            .AddMicrosoftGraph(Configuration.GetSection("DownstreamApi"))
            .AddInMemoryTokenCaches();
    
        services.AddControllersWithViews(options =>
        {
            var policy = new AuthorizationPolicyBuilder()
                .RequireAuthenticatedUser()
                .Build();
            options.Filters.Add(new AuthorizeFilter(policy));
        });
        services.AddRazorPages()
                .AddMicrosoftIdentityUI();
    }
    

    DownstreamApi Under appsettings.json:

    "DownstreamApi": {
        "BaseUrl": "https://graph.microsoft.com/v1.0",
        "Scopes": "https://graph.microsoft.com/.default"
      }
    

    Output:

    enter image description here

    Note: Make sure, you have included app.UseAuthentication(); in program.cs or startup.cs file in order to get expected result.