Как получить токен доступа после проверки подлинности с использованием потока проверки подлинности платформы Microsoft Identity?

В моем program.cs в .NET 6 это код, который я использую для проверки подлинности Azure AD.

string[] initialScopes = builder.Configuration.GetValue<string>("DownstreamApi:Scopes")?.Split(' ');

    builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
    .AddMicrosoftIdentityWebApp(options => {
        builder.Configuration.GetSection("AzureAd").Bind(options);

        options.NonceCookie.SecurePolicy = CookieSecurePolicy.Always;
        options.CorrelationCookie.SecurePolicy = CookieSecurePolicy.Always;
    });

Как получить токен доступа после проверки подлинности с использованием потока проверки подлинности платформы Microsoft Identity? Мне нужен токен доступа в последующих запросах для доступа к защищенным ресурсам без необходимости использовать JavaScript для получения нового токена доступа каждый раз.

У меня уже есть clientId, tenantId и clientSecret.

Этот код взят из https://learn.microsoft.com/en-us/azure/active-directory/develop/scenario-web-api-call-api-acquire-token?tabs=aspnetcore

[Authorize]
public class MyApiController : Controller
{
    /// <summary>
    /// The web API will accept only tokens 1) for users, 2) that have the `access_as_user` scope for
    /// this API.
    /// </summary>
    static readonly string[] scopeRequiredByApi = new string[] { "access_as_user" };

     static readonly string[] scopesToAccessDownstreamApi = new string[] { "api://MyTodolistService/access_as_user" };

    private readonly ITokenAcquisition _tokenAcquisition;

    public MyApiController(ITokenAcquisition tokenAcquisition)
    {
        _tokenAcquisition = tokenAcquisition;
    }

    public IActionResult Index()
    {
        HttpContext.VerifyUserHasAnyAcceptedScope(scopeRequiredByApi);

        string accessToken = _tokenAcquisition.GetAccessTokenForUserAsync(scopesToAccessDownstreamApi);
        return await callTodoListService(accessToken);
    }
}

Как получить scopeRequiredByApi и scopesToAccessDownstreamApi?

Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
4
0
344
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

В своем запуске добавьте строку .EnableTokenAcquisitionToCallDownstreamApi() и выберите реализацию кэша токенов, например .AddInMemoryTokenCaches().

builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
    .AddMicrosoftIdentityWebApp(options => {
        builder.Configuration.GetSection("AzureAd").Bind(options);

        options.NonceCookie.SecurePolicy = CookieSecurePolicy.Always;
        options.CorrelationCookie.SecurePolicy = CookieSecurePolicy.Always;
    })
    .EnableTokenAcquisitionToCallDownstreamApi()
    .AddDownstreamWebApi("MyApi", Configuration.GetSection("MyApiConfig"))
    .AddInMemoryTokenCaches();

Внедрите интерфейс ITokenAcquisition в свой контроллер/сервис. Чтобы получить токен доступа, вызовите:

string token = await _tokenAcquisition.GetAccessTokenForUserAsync(scopes);

Из справки Microsoft :

Обновлено: для последующего вопроса, добавленного позже

Как получить scopeRequiredByApi и scopesToAccessDownstreamApi?

На портале Azure перейдите к App Registrations и откройте регистрацию приложения API. Перейти в раздел Expose an API. Вы должны найти свои прицелы там.

откуда я могу получить значение scopes? я обновил свой пост

Steve 11.04.2023 00:27

это будет задокументировано в API, к которому вы пытаетесь получить доступ

YK1 11.04.2023 02:08

Я уже вижу эту строку в вашем коде: string[] initialScopes = builder.Configuration.GetValue<string>("DownstreamApi:Scopes‌​")?.Split(' ');

YK1 11.04.2023 02:12

Меня немного смущает scopes. Да, у меня есть initialScopes, и я поставил значение «user.read», которое, как мне кажется, и есть scopeRequiredByApi . А как же scopesToAccessDownstreamApi? Как мне это получить?

Steve 11.04.2023 02:31

На портале Azure перейдите к App Registrations и откройте регистрацию приложения API. Перейти к Expose an API. Вы должны найти свой диапазон там.

YK1 11.04.2023 03:33
Ответ принят как подходящий

Не уверен, что хорошо начинать отсюда. Для авторизации Azure AD (создания токена доступа) существует несколько потоков, наиболее часто используется поток кода аутентификации . Позвольте пользователю войти в систему, затем он может использовать учетные данные для создания токена доступа с разрешением delegated API, токен будет иметь scp претензия. Поток учетных данных клиента - не нужно входить в систему, сгенерируйте токен с application разрешением API от имени самого приложения, токен будет содержать roles требование). Поток от имени — используется, когда API requires to call another API обоим API требуется токен доступа).

О API permission type, например, Microsoft graph создает API календаря , используя делегированное разрешение API, можно обновлять ресурс для самого пользователя. Но с помощью API приложения приложение может изменить любое событие календаря пользователя, находящегося в целевом арендаторе. Поскольку мы можем set Azure AD to protect our own API, мы также можем создавать разрешения API в Azure AD, выбирая предоставление делегированного разрешения API или предоставление разрешения API приложения.

Согласно ссылке, которой вы поделились в вопросе, вы пытаетесь использовать поток от имени, поскольку документ представляет собой руководство для одного API, вызывающего другой API. Тогда, пожалуйста, позвольте мне сделать предположение здесь. У вас есть клиентское приложение, которое интегрировало MSAL, чтобы вы могли позволить пользователям входить в систему и получать токен доступа, вы использовали этот токен для вызова собственного API, ваш API требует вызова другого API для получения некоторой другой информации.

Как я уже сказал, Azure AD может защитить наш собственный веб-API. Итак, мы можем перейти к Azure AD -> App Registrations -> create an Azure AD app or choose an existing one -> Expose an API -> after create the API, add a scope and name it like Steve_Allowed, поскольку предполагается, что пользователь вызывает API, поэтому я создаю область для предоставления делегированного API. Тогда в same Azure AD application -> API permissions -> Add a permission -> My APIs -> choose the AAD app -> Delegated permissions -> choose Steve_Allowed and click Add permissions -> click grant admin consent button beside add api permission button. Далее вам также необходимо создать секрет клиента в Certificates & secrets лезвии, если у вас его нет.

Теперь с настройками в Azure AD мы уже закончили, переходим к приложению API. Как вы знаете, когда вы хотите вызвать API, защищенный Azure AD, вам нужно добавить заголовок запроса, например Authorization: Bearer xxxx. В наш веб-API нам нужно интегрировать Azure AD для проверки запроса, чтобы он мог дать вам правильный ответ или ошибку 401. Итак, в проекте API добавьте код, как показано ниже, но, возможно, вы уже сделали это.

using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.Identity.Web;

...
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
   .AddMicrosoftIdentityWebApi(builder.Configuration.GetSection("AzureAd"))
   .EnableTokenAcquisitionToCallDownstreamApi()//this 2 lines for using _tokenAcquisition to generate another access token
   .AddInMemoryTokenCaches();
builder.Services.AddHttpClient();

...
app.UseAuthentication();
app.UseAuthorization();


 "AzureAd": {
    "Instance": "https://login.microsoftonline.com/",
    "ClientId": "azure_ad_app_id",
    "ClientSecret": "client_secret",
    "Domain": "tenant_id",
    "TenantId": "tenant_id",
    "Audience": "api://client_id"
  },

Предположим, что он содержит API, который добавил атрибут [Authorize] с URL-адресом https://localhost:7072/WeatherForecast, затем добавьте заголовок запроса. В своем коде API вы можете использовать приведенный ниже код, чтобы получить токен в заголовке запроса.

StringValues authorizationToken;
HttpContext.Request.Headers.TryGetValue("Authorization", out authorizationToken);
string incomingToken = authorizationToken.ToString().Replace("Bearer ", "");

И здесь входящий Токен содержал нужный вам scopeRequiredByApi. Но он уже валидирован к середине, так что метод писать не нужно VerifyUserHasAnyAcceptedScope для проверки токена.

Теперь мы успешно получили доступ к API, а затем нам нужно использовать поток on_behalf_of для доступа к другому API. Поскольку промежуточное ПО уже выполнило проверку за нас, мы можем напрямую использовать _tokenAcquisition в нашем коде, чтобы сгенерировать еще один токен доступа и использовать его для вызова другого API. Таким образом, в вашем API вы можете иметь код, как показано ниже. Обратите внимание, что поскольку вы пытаетесь вызвать другой API, если этот API также управляется вами, вам может потребоваться открыть другой API, тогда вы сможете получить новую область действия. Если этот API управляется другими, например API создания календаря, вам нужно перейти к документу API, чтобы получить область действия Calendars.ReadWrite. Поставщик API должен предоставить вам область, и эта область — это то, что вам нужно, scopesToAccessDownstreamApi.

private readonly ITokenAcquisition _tokenAcquisition;
private readonly IHttpClientFactory _httpClientFactory;

public WeatherForecastController(ITokenAcquisition tokenAcquisition, IHttpClientFactory httpClientFactory)
{
    _tokenAcquisition = tokenAcquisition;
    _httpClientFactory = httpClientFactory;
}

[HttpGet(Name = "GetWeatherForecast")]
public async Task<string> GetAsync()
{
    var accessToken = await _tokenAcquisition.GetAccessTokenForUserAsync(new string[] { "api://client_id/scopeName" });

    var httpRequestMessage = new HttpRequestMessage(HttpMethod.Get, "https://localhost:7212/WeatherForecast")
    {
        Headers =
        {
            { HeaderNames.Authorization, "Bearer "+ accessToken}
        }
    };

    var httpClient = _httpClientFactory.CreateClient();
    var response = await httpClient.SendAsync(httpRequestMessage);

    var res = "";
    if (response.StatusCode == HttpStatusCode.OK)
    {
        res = await response.Content.ReadAsStringAsync();
    }
    return "hello";
}

===========================Резюме====================== ====

Вам нужно, чтобы один API вызывал другой API, если оба API управляются вами, вам лучше создать 2 приложения Azure AD и предоставить разрешение API в обоих из них. Если вы хотите вызвать какой-либо API, вам необходимо установить область действия для создания маркера доступа для API. Например, вы предоставили разрешение API для API 1, область действия которого должна выглядеть как api://client_id1/scope_name_for_API1. Затем используйте его в заголовке запроса и вызовите API 1, в API 1 вы хотите вызвать API 2, затем вам нужно использовать _tokenAcquisition для генерации с помощью api://client_id2/scope_name_for_API2 для API 2 и вызвать его.

Другие вопросы по теме