Как отправить запрос на получение доступных дисков в graph api используя токен приложения

У меня есть web api .net core приложение. Которое принимает запросы от клиента и дальше стучится в graph api. Когда я пытаюсь получить список дисков в graph api используя токен, полученный после авторизации пользователя, все работает хорошо. Пример:

var drives = await _graphServiceClient.Me.Drives.Request().GetAsync();

Когда я пытаюсь получить тоже самое, используя неявную авторизацию (токен приложения), получаю ошибку от graph api: Microsoft.Graph.ServiceException: Code: generalException Message: An error occurred sending the request. Пример:

var drives = await _graphServiceClient.Drives.Request().GetAsync();

При этом, попробовал аналогичный запрос в Online REST & SOAP API Testing Tool, все работает: введите сюда описание изображения

UPD:

Вот мой конфиг файл:

{
  "AzureAd": {
    "Instance": "https://login.microsoftonline.com/",
    "Domain": "company.eu",
    "TenantId": "******-7906-447b-****-d7fdcd7a90d8",
    "ClientId": "******-7a40-4ba7-****-f3546c0ba1f9",
    "ClientSecret": "**********.V.A0gm-yU0D7xHQRg"
  },
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "AllowedHosts": "*",
  "MicrosoftGraph": {
    "Scopes": "user.read",
    "BaseUrl": "https://graph.microsoft.com/v1.0"
  }
}

Здесь конфигурация в файле Startup.cs:

public void ConfigureServices(IServiceCollection services)
{
                
   services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
                    .AddMicrosoftIdentityWebApi(Configuration.GetSection("AzureAd"))
                    .EnableTokenAcquisitionToCallDownstreamApi()
                    .AddMicrosoftGraph(Configuration.GetSection("MicrosoftGraph"))
                    .AddInMemoryTokenCaches();
    
   services.AddControllers();
}

Есть два роута:

//действия в graph api c токеном юзера(после авторизации) 
[Authorize]
[HttpPost]
[DisableRequestSizeLimit]
[Route("uploadasuser")]
public async Task<ActionResult> PostUploadFileAsUser()
{
   var drives = await _graphServiceClient.Me.Drives.Request().GetAsync();
}

//действия в graph api c токеном приложения(неявная авторизация)
[HttpPost]
[DisableRequestSizeLimit]
[Route("uploadasapplication")]
public async Task<ActionResult> PostUploadFileAsApplication()
{
    var drives = await _graphServiceClient.Drives.Request().GetAsync();
}

Если добавить в оба роута

Request.Headers.TryGetValue("Authorization", out var token);

Вижу что токены прилетают вместе с запросом. Токены точно валидные. Однако первый роут где я обращаюсь с токеном пользователя, отрабатывает как надо. А во втором, где я обращаюсь с токеном приложения, я получаю ошибку, полный стек ошибки:

Microsoft.Graph.ServiceException: Code: generalException
Message: An error occurred sending the request.

 ---> System.NullReferenceException: Object reference not set to an instance of an object.
   at Microsoft.Identity.Web.MergedOptions.PrepareAuthorityInstanceForMsal()
   at Microsoft.Identity.Web.TokenAcquisition.BuildConfidentialClientApplication(MergedOptions mergedOptions)
   at Microsoft.Identity.Web.TokenAcquisition.GetOrBuildConfidentialClientApplication(MergedOptions mergedOptions)
   at Microsoft.Identity.Web.TokenAcquisition.GetAuthenticationResultForUserAsync(IEnumerable`1 scopes, String authenticationScheme, String tenantId, String userFlow, ClaimsPrincipal user, TokenAcquisitionOptions tokenAcquisitionOptions)
   at Microsoft.Identity.Web.TokenAcquisition.GetAccessTokenForUserAsync(IEnumerable`1 scopes, String authenticationScheme, String tenantId, String userFlow, ClaimsPrincipal user, TokenAcquisitionOptions tokenAcquisitionOptions)
   at Microsoft.Identity.Web.TokenAcquisitionAuthenticationProvider.AuthenticateRequestAsync(HttpRequestMessage request)
   at Microsoft.Graph.AuthenticationHandler.SendAsync(HttpRequestMessage httpRequestMessage, CancellationToken cancellationToken)
   at System.Net.Http.HttpClient.SendAsyncCore(HttpRequestMessage request, HttpCompletionOption completionOption, Boolean async, Boolean emitTelemetryStartStop, CancellationToken cancellationToken)
   at Microsoft.Graph.HttpProvider.SendRequestAsync(HttpRequestMessage request, HttpCompletionOption completionOption, CancellationToken cancellationToken)
   --- End of inner exception stack trace ---
   at Microsoft.Graph.HttpProvider.SendRequestAsync(HttpRequestMessage request, HttpCompletionOption completionOption, CancellationToken cancellationToken)
   at Microsoft.Graph.HttpProvider.SendAsync(HttpRequestMessage request, HttpCompletionOption completionOption, CancellationToken cancellationToken)
   at Microsoft.Graph.BaseRequest.SendRequestAsync(Object serializableObject, CancellationToken cancellationToken, HttpCompletionOption completionOption)
   at Microsoft.Graph.BaseRequest.SendAsync[T](Object serializableObject, CancellationToken cancellationToken, HttpCompletionOption completionOption)
   at Microsoft.Graph.GraphServiceDrivesCollectionRequest.GetAsync(CancellationToken cancellationToken)
   at MediaGun.Controllers.Api.FilesController.UploadFileToCloud(MemoryStream proccesedPresentation, String fileName, Boolean uploadAsUser) in E:\MediaGun\Controllers\Api\FilesController.cs:line 369
   at MediaGun.Controllers.Api.FilesController.PostUploadFileAsApplication() in E:\MediaGun\Controllers\Api\FilesController.cs:line 131
   at lambda_method80(Closure , Object )
   at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.TaskOfActionResultExecutor.Execute(IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeActionMethodAsync>g__Logged|12_1(ControllerActionInvoker invoker)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeNextActionFilterAsync>g__Awaited|10_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Rethrow(ActionExecutedContextSealed context)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeInnerFilterAsync>g__Awaited|13_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeFilterPipelineAsync>g__Awaited|19_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Logged|17_1(ResourceInvoker invoker)
   at Microsoft.AspNetCore.Routing.EndpointMiddleware.<Invoke>g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger)
   at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
   at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.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) 

Скорее всего что-то с конфигурацией, но я не могу понять что именно.


Ответы (1 шт):

Автор решения: Александр Климук

Для неявной авторизации (используя токен приложения) создал свой GraphServiceClient.

Добавил метод расширения:

public static void AddAzureGraphApiClientConfig(this IServiceCollection services, IConfiguration configuration)
{
    services.AddSingleton(x => 
    { 
        var microsoftAzureAdConstant = configuration.GetSection("AzureAD");
    
        var scopes = new[] { microsoftAzureAdConstant["Scope"] };

        var options = new TokenCredentialOptions
        {
            AuthorityHost = AzureAuthorityHosts.AzurePublicCloud
        };

        var clientSecretCredential = new ClientSecretCredential(
            microsoftAzureAdConstant["TenantId"], 
            microsoftAzureAdConstant["ClientId"], 
            microsoftAzureAdConstant["ClientSecret"], options);

        return new GraphServiceClient(clientSecretCredential, scopes);
    });
}

Далее, добавляем сервис в ConfigureServices и можем использовать его в любой части приложения.

→ Ссылка