Search code examples
c#azure-active-directoryasp.net-core-mvcmicrosoft-graph-apiasp.net-core-webapi

ASP.NET Core 5 WebAPI - Azure AD - Call Graph API Using Azure Ad Access Token


I have created a WebAPI using .NET 5 using Azure AD for authentication of the user. I am using Postman to generate the Access Token and the WebAPI Works when passing Access Token as Bearer.

I want to call Graph API to get the user Calendar & Profile using the Access Token but unable to do so. I have followed few articles mentioned below with no luck.

Invalid Authentication Token error when using the Access Token to call /me Graph API enter image description here

App registration to use My Org Only. I have added Delegated permissions to Graph API.

enter image description here

Created A custom scope using Expose An API as the WebAPI Was throwing Signature Invalid Error.

My WebAPI Configure Services in StartUp.cs Code:

services.AddMicrosoftIdentityWebApiAuthentication(Configuration);

My AppSettings.json

  "AzureAd": {
    "Instance": "https://login.microsoftonline.com/",
    "Domain": "<Domain",
    "ClientId": "<ClientId>",
    "TenantId": "<TenantId>"
  },

I have tried to follow couple of articles which suggest On Behalf Of Flow

https://joonasw.net/view/azure-ad-on-behalf-of-aspnet-core

Struggle with MS Graph and asp.net Core API

I am lost any help is much appreciated.

EDIT1:

I tried Implementing the solution suggested by Farid. The postman output was returning HTML Response to Sign In. It is a ASP.NET Core 5 WebAPI so I changed the ConfigureServices code to below:

services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)              
         .AddMicrosoftIdentityWebApi(Configuration.GetSection("AzureAd"))
         .EnableTokenAcquisitionToCallDownstreamApi()
         .AddMicrosoftGraph(Configuration.GetSection("DownstreamApi"))
         .AddInMemoryTokenCaches();

I receive the following Error.

System.InvalidOperationException: No authenticationScheme was specified, and there was no DefaultChallengeScheme found. The default schemes can be set using either AddAuthentication(string defaultScheme) or AddAuthentication(Action<AuthenticationOptions> configureOptions).
   at Microsoft.AspNetCore.Authentication.AuthenticationService.ChallengeAsync(HttpContext context, String scheme, AuthenticationProperties properties)
   at Microsoft.AspNetCore.Authorization.Policy.AuthorizationMiddlewareResultHandler.HandleAsync(RequestDelegate next, HttpContext context, AuthorizationPolicy policy, PolicyAuthorizationResult authorizeResult)
   at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
   at Swashbuckle.AspNetCore.SwaggerUI.SwaggerUIMiddleware.Invoke(HttpContext httpContext)
   at Swashbuckle.AspNetCore.Swagger.SwaggerMiddleware.Invoke(HttpContext httpContext, ISwaggerProvider swaggerProvider)
   at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)

Update:

I was able to resolve the error by using below code:

services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)                 .AddMicrosoftIdentityWebApi(Configuration.GetSection("AzureAd"))
.EnableTokenAcquisitionToCallDownstreamApi()                    .AddMicrosoftGraph(Configuration.GetSection("DownstreamApi"))
.AddInMemoryTokenCaches();

I am able to read my profile but not able to obtain Calendar Items. It could be due to Admin Consent Requirement.


Solution

  • As per your given information it seems your token has expired, so firstly try to regenerate that, additionally, you have added Delegated permissions that's fine but I doubt you haven added the grant admin consent after adding Graph API permission. So its mandatory and please double check that. See the below screen capture. Please follow the below steps to get token and user info using Microsoft Graph API

    Grant Admin Consent:

    enter image description here

    Token Aquisition Example:

    Check the appsettings.json it should be like below:

    {
      "AzureAd": {
        "Instance": "https://login.microsoftonline.com/",
        "Domain": "yourdemain.onmicrosoft.com",
        "TenantId": "",
        "ClientId": "",
        "ClientSecret": "",
        "ClientCertificates": [
        ],
        "CallbackPath": "/signin-oidc"
      },
      "DownstreamApi": {
    
        "BaseUrl": "https://graph.microsoft.com/v1.0",
        "Scopes": "https://graph.microsoft.com/.default"
      },
      "Logging": {
        "LogLevel": {
          "Default": "Information",
          "Microsoft": "Warning",
          "Microsoft.Hosting.Lifetime": "Information"
        }
      },
      "AllowedHosts": "*"
    }
    

    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();
            }
    

    Note: You can ignore below two service from the ConfigureServices under startup.cs .EnableTokenAcquisitionToCallDownstreamApi(initialScopes) .AddMicrosoftGraph(Configuration.GetSection("DownstreamApi")) because I am using Graph API SDK and reading scope from appsettings.json so in the sample I am using local scope for your test case. So its not required or upto you.

    Controller Action:

    public async Task<object> GetUserInfoFromGraphAPI()
            {
    
                try
                {
    
                    //Initialize on behalf of user token aquisition service
    
                    var _tokenAcquisition = this.HttpContext.RequestServices.GetRequiredService<ITokenAcquisition>() as ITokenAcquisition;
    
                    //define the scope
                    string[] scopes = new string[] { "https://graph.microsoft.com/.default" };
    
                    //Getting token from Azure Active Directory
                    string accessToken = await _tokenAcquisition.GetAccessTokenForUserAsync(scopes);
    
                    //Request Grap API end point
                    HttpClient _client = new HttpClient();
                    HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, string.Format("https://graph.microsoft.com/v1.0/me"));
    
                    //Passing Token For this Request
                    request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
                    HttpResponseMessage response = await _client.SendAsync(request);
    
                    //Get User into from grpah API
                    dynamic userInfo = JsonConvert.DeserializeObject<dynamic>(await response.Content.ReadAsStringAsync());
    
    
                    return userInfo;
                }
                catch (Exception ex)
                {
    
                    throw;
                }
    
            }
    

    Output:

    enter image description here

    Note: For complete solution please visit the GitHub Link

    Hope it will help you to achive your goal.