Добавьте аутентификацию Microsoft Entra ID в приложение .NET Aspire

В этом руководстве показано, как защитить распределенное приложение .NET Aspire с помощью Microsoft Entra ID аутентификации и авторизации. В нем рассматриваются следующие сведения:

  1. Интерфейс Blazor Server (MyService.Web): вход пользователя с помощью OpenID Connect и получение токенов
  2. Защищенная серверная часть API (MyService.ApiService): проверка JWT с помощью Microsoft.Identity.Web
  3. Сквозной поток: Blazor получает токены доступа и вызывает защищенный API с помощью обнаружения службы Aspire.

В этом руководстве предполагается, что вы начали работу с проектом Aspire, созданным с помощью следующей команды:

aspire new aspire-starter --name MyService

Необходимые условия

  • пакет SDK .NET 9 или более поздней версии
  • .NET Aspire CLI — см. раздел Install Aspire CLI
  • клиент Microsoft Entra/c0 — сведения о настройке приложений Register в Microsoft Entra ID

Подсказка

Вы новичок в Aspire? См. обзор .NET Aspire.

Общие сведения о двухфазном рабочем процессе

В этом руководстве используется двухэтапный подход:

Phase Что происходит Результат
Этап 1 Добавить код аутентификации со значениями-заполнителями Приложение собирается, но не запускается
Этап 2 Создание регистраций приложений Microsoft Entra Запуск приложения с реальной проверкой подлинности

Регистрация приложений в Microsoft Entra ID

Прежде чем приложение сможет пройти проверку подлинности пользователей, вам потребуется две регистрации приложений в Microsoft Entra:

Регистрация приложений Purpose Конфигурация ключа
API (MyService.ApiService) Проверяет входящие токены URI идентификатора приложения, access_as_user область действия
Веб-приложение (MyService.Web) Авторизация пользователей, получение токенов URI для перенаправления, секрет клиента, разрешения API

Если у вас уже настроены регистрации приложений, вам потребуются следующие значения:appsettings.json

  • TenantId — идентификатор клиента Microsoft Entra
  • Api ClientId — идентификатор приложения (клиента) регистрации приложения API
  • URI идентификатора приложения API — обычно api://<api-client-id> (используется Audiences и Scopes)
  • Web App ClientId — идентификатор приложения (клиента) регистрации веб-приложения
  • Секрет клиента (или сертификат) — учетные данные для веб-приложения (хранятся в секретах пользователя, а не appsettings.json)

Шаг 1. Регистрация API

  1. Перейдите к Центр администрирования Microsoft Entra>Identity>Applications>Регистрация приложений.
  2. Выберите Новая регистрация.
    • Имя: MyService.ApiService
    • Поддерживаемые типы учетных записей: Учетные записи в этом каталоге организации (только один клиент)
    • Выберите Зарегистрировать.
  3. Перейдите к Экспонирование API>, затем Добавить рядом с URI идентификатора приложения.
    • Примите значение по умолчанию (api://<client-id>) или настройте его.
    • Выберите Добавить область:
      • Имя области:access_as_user
      • Кто может согласиться: Администраторы и пользователи
      • Отображаемое имя согласия администратора: Доступ к API MyService
      • Описание согласия администратора: Позволяет приложению получить доступ к API MyService от имени пользователя, вошедшего в систему.
      • Выберите Добавить область.
  4. Скопируйте идентификатор приложения (клиента) — вам потребуется это для обоих appsettings.json файлов.

Дополнительные сведения см. в кратком руководстве по настройке приложения для предоставления веб-API.

Шаг 2. Регистрация веб-приложения

  1. Перейдите к Регистрация приложений>Новая регистрация.
    • Имя: MyService.Web
    • Поддерживаемые типы учетных записей: Учетные записи только в этом каталоге организации
    • URI перенаправления: Выберите Веб-адрес и введите URL-адрес приложения + /signin-oidc
      • Для локальной разработки: https://localhost:7001/signin-oidc (проверьте launchSettings.json на фактический номер порта)
    • Выберите Зарегистрировать.
  2. Перейдите к Проверка подлинности>Добавить URI, чтобы добавить все URL-адреса вашей разработки (из launchSettings.json).
  3. Перейдите в раздел "Сертификаты и секреты>>, "Новый секрет клиента".
    • Добавьте описание и срок действия.
    • Скопируйте значение секрета немедленно— он не будет отображаться снова.
  4. Перейдите к разрешениям> API, чтобы добавить разрешение>на мои API.
    • Выберите параметр MyService.ApiService.
    • Выберите " access_as_user>Добавить разрешения".
    • Выберите Предоставить согласие администратора для [тенанта] (или пользователям будет предложено сделать это при первом использовании).
  5. Скопируйте идентификатор приложения (клиента) для веб-приложения appsettings.json.

Замечание

Некоторые организации не разрешают использование секретных ключей клиентов. Дополнительные сведения см. в разделе "Учетные данные сертификата " или "Проверка подлинности без сертификата".

Дополнительные сведения см. в кратком руководстве по регистрации приложения.

Шаг 3. Обновление конфигурации

После создания регистраций для приложений обновите appsettings.json файлы:

API (MyService.ApiService/appsettings.json):

{
  "AzureAd": {
    "Instance": "https://login.microsoftonline.com/",
    "TenantId": "YOUR_TENANT_ID",
    "ClientId": "YOUR_API_CLIENT_ID",
    "Audiences": ["api://YOUR_API_CLIENT_ID"]
  }
}

Веб-приложение (MyService.Web/appsettings.json):

{
  "AzureAd": {
    "Instance": "https://login.microsoftonline.com/",
    "TenantId": "YOUR_TENANT_ID",
    "ClientId": "YOUR_WEB_CLIENT_ID",
    "CallbackPath": "/signin-oidc",
    "ClientCredentials": [
      { "SourceType": "ClientSecret" }
    ]
  },
  "WeatherApi": {
    "Scopes": ["api://YOUR_API_CLIENT_ID/.default"]
  }
}

Безопасно храните секрет:

cd MyService.Web
dotnet user-secrets set "AzureAd:ClientCredentials:0:ClientSecret" "YOUR_SECRET_VALUE"
Ценность Где найти
TenantId Центр администрирования Microsoft Entra > Обзор > Идентификатор клиента
API ClientId Регистрация приложений > MyService.ApiService > Идентификатор приложения (клиента)
Web ClientId Регистрация приложений > MyService.Web > Идентификатор приложения (клиента)
Client Secret Создано на шаге 2 (копировать сразу после создания)

Замечание

Шаблон начального WeatherApiClient элемента Aspire автоматически создает класс в MyService.Web проекте. Этот типизированный HttpClient используется в этом руководстве для демонстрации вызова защищенного API. Вам не нужно самостоятельно создавать этот класс — это часть шаблона.


Начните быстро

Этот раздел содержит краткий справочник по добавлению аутентификации. Подробные пошаговые руководства см. в разделе "Часть 1 " и "Часть 2".

API (MyService.ApiService)

Установите пакет Microsoft.Identity.Web NuGet:

dotnet add package Microsoft.Identity.Web

Добавьте конфигурацию Microsoft Entra в appsettings.json:

{
  "AzureAd": {
    "Instance": "https://login.microsoftonline.com/",
    "TenantId": "<tenant-id>",
    "ClientId": "<api-client-id>",
    "Audiences": ["api://<api-client-id>"]
  }
}

Регистрация аутентификации и авторизации в Program.cs:

builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddMicrosoftIdentityWebApi(builder.Configuration.GetSection("AzureAd"));
builder.Services.AddAuthorization();
// ...
app.UseAuthentication();
app.UseAuthorization();
// ...
app.MapGet("/weatherforecast", () => { /* ... */ }).RequireAuthorization();

Веб-приложение (MyService.Web)

Установите пакет Microsoft.Identity.Web NuGet:

dotnet add package Microsoft.Identity.Web

Добавьте настройку Microsoft Entra в appsettings.json:

{
  "AzureAd": {
    "Instance": "https://login.microsoftonline.com/",
    "TenantId": "<tenant-id>",
    "ClientId": "<web-client-id>",
    "CallbackPath": "/signin-oidc",
    "ClientCredentials": [{ "SourceType": "ClientSecret" }]
  },
  "WeatherApi": { "Scopes": ["api://<api-client-id>/.default"] }
}

Настройка аутентификации, получения токена и клиента конечного API в Program.cs:

builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
    .AddMicrosoftIdentityWebApp(builder.Configuration.GetSection("AzureAd"))
    .EnableTokenAcquisitionToCallDownstreamApi()
    .AddInMemoryTokenCaches();

builder.Services.AddCascadingAuthenticationState();
builder.Services.AddScoped<BlazorAuthenticationChallengeHandler>();

builder.Services.AddHttpClient<WeatherApiClient>(client =>
    client.BaseAddress = new("https+http://apiservice"))
    .AddMicrosoftIdentityMessageHandler(builder.Configuration.GetSection("WeatherApi"));
// ...
app.UseAuthentication();
app.UseAuthorization();
app.MapGroup("/authentication").MapLoginAndLogout();

Автоматически MicrosoftIdentityMessageHandler получает и присоединяет токены, а BlazorAuthenticationChallengeHandler обрабатывает проблемы согласия и условного доступа.

Это важно

Не забудьте создать UserInfo.razor для кнопки входа. Дополнительные сведения см. в разделе "Добавление компонентов пользовательского интерфейса Blazor ".

Замечание

BlazorAuthenticationChallengeHandler и LoginLogoutEndpointRouteBuilderExtensions включены в Microsoft.Identity.Web (v3.3.0+). Копирование файлов не требуется.


Определение файлов для изменения

В следующей таблице перечислены файлы, которые вы изменяете в каждом проекте:

Проект Файл Изменения
ApiService Program.cs Проверка подлинности носителя JWT, ПО промежуточного слоя авторизации
appsettings.json Конфигурация Microsoft Entra
.csproj Добавьте Microsoft.Identity.Web
Паутина Program.cs Аутентификация OIDC, получение токена, BlazorAuthenticationChallengeHandler
appsettings.json Microsoft Entra конфигурация, внешние области API
.csproj Добавьте Microsoft.Identity.Web (версия 3.3.0+)
Components/UserInfo.razor Пользовательский интерфейс кнопки входа (новый файл)
Components/Layout/MainLayout.razor Включение компонента UserInfo
Components/Routes.razor АвторизацияRouteView для защищенных страниц
Страницы, вызывающие API Использование try/catch с ChallengeHandler

Общие сведения о потоке проверки подлинности

На следующей схеме показано, как интерфейс Blazor, Microsoft Entra и защищенный API взаимодействуют:

flowchart LR
  A[User Browser] -->|1 Login OIDC| B[Blazor Server<br/>MyService.Web]
  B -->|2 Redirect| C[Microsoft Entra ID]
  C -->|3 auth code| B
  B -->|4 exchange auth code| C
  C -->|5 tokens| B
  B -->|6 cookie + session| A
  B -->|7 HTTP + Bearer token| D[ASP.NET API<br/>MyService.ApiService<br/>Microsoft.Identity.Web]
  D -->|8 Validate JWT| C
  D -->|9 Weather data| B
  1. Пользователь посещает приложение Blazor → Неавторизован → видит кнопку "Вход".
  2. пользователь выбирает вход → Перенаправление на /authentication/login → вызов OIDC → Microsoft Entra.
  3. Пользователь входит в систему → Microsoft Entra выполняет перенаправление /signin-oidc → установлен файл cookie.
  4. Пользователь переходит на страницу погоды → Blazor совершает вызовыWeatherApiClient.GetAsync().
  5. MicrosoftIdentityMessageHandler перехватывает запрос, получает токен из кэша (или обновляет его без уведомления) и добавляет заголовок Authorization: Bearer <token>.
  6. API получает запрос → Microsoft. Identity.Web проверяет → JWT возвращает данные.
  7. Blazor отображает данные о погоде.

Оценка структуры решения

Начальный шаблон Aspire создает следующий макет проекта:

MyService/
├── MyService.AppHost/           # Aspire orchestration
├── MyService.ApiService/        # Protected API (Microsoft.Identity.Web)
├── MyService.Web/               # Blazor Server (Microsoft.Identity.Web)
├── MyService.ServiceDefaults/   # Shared defaults
└── MyService.Tests/             # Tests

Часть 1. Защита серверной части API с помощью Microsoft. Identity.Web

Этот раздел настраивает проект API для проверки маркеров доступа JWT, выданных Microsoft Entra.

Добавьте пакет Microsoft.Identity.Web

Выполните следующую команду, чтобы установить пакет Microsoft.Identity.Web NuGet.

cd MyService.ApiService
dotnet add package Microsoft.Identity.Web

Настройка параметров Microsoft Entra

Добавьте конфигурацию Microsoft Entra в MyService.ApiService/appsettings.json:

{
  "AzureAd": {
    "Instance": "https://login.microsoftonline.com/",
    "TenantId": "<your-tenant-id>",
    "ClientId": "<your-api-client-id>",
    "Audiences": [
      "api://<your-api-client-id>"
    ]
  },
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*"
}

Ключевые свойства:

  • ClientId: идентификатор регистрации приложения API Microsoft Entra
  • TenantId: идентификатор клиента Microsoft Entra или "organizations" для нескольких арендаторов или "common" для любой учетной записи Microsoft
  • Audiences: допустимые аудитории токенов (обычно URI App ID)

Обновите API Program.cs

Замените содержимое MyService.ApiService/Program.cs следующим кодом, чтобы добавить проверку подлинности носителя JWT и защитить конечные точки:

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

var builder = WebApplication.CreateBuilder(args);

builder.AddServiceDefaults();

// Add Microsoft.Identity.Web JWT Bearer authentication
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddMicrosoftIdentityWebApi(builder.Configuration.GetSection("AzureAd"));

builder.Services.AddProblemDetails();
builder.Services.AddOpenApi();
builder.Services.AddAuthorization();

var app = builder.Build();

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

if (app.Environment.IsDevelopment())
{
    app.MapOpenApi();
}

string[] summaries = ["Freezing", "Bracing", "Chilly", "Cool", "Mild",
    "Warm", "Balmy", "Hot", "Sweltering", "Scorching"];

app.MapGet("/", () =>
    "API service is running. Navigate to /weatherforecast to see sample data.");

app.MapGet("/weatherforecast", () =>
{
    var forecast = Enumerable.Range(1, 5).Select(index =>
        new WeatherForecast
        (
            DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
            Random.Shared.Next(-20, 55),
            summaries[Random.Shared.Next(summaries.Length)]
        ))
        .ToArray();
    return forecast;
})
.WithName("GetWeatherForecast")
.RequireAuthorization();

app.MapDefaultEndpoints();
app.Run();

record WeatherForecast(DateOnly Date, int TemperatureC, string? Summary)
{
    public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
}

Ключевые изменения:

  • Регистрация проверки подлинности носителя JWT с помощью AddMicrosoftIdentityWebApi
  • Добавьте app.UseAuthentication() и app.UseAuthorization() ПО промежуточного слоя
  • Применение .RequireAuthorization() к защищенным конечным точкам

Тестирование защищенного API

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

Отправьте запрос без токена.

curl https://localhost:<PORT>/weatherforecast
# Expected: 401 Unauthorized

Отправьте запрос с допустимым маркером:

curl -H "Authorization: Bearer <TOKEN>" https://localhost:<PORT>/weatherforecast
# Expected: 200 OK with weather data

Часть 2. Настройка интерфейса Blazor для проверки подлинности

Приложение Blazor Server использует Microsoft.Identity.Web:

  • Вход пользователей с помощью OIDC
  • Получение маркеров доступа для вызова API
  • Присоединение маркеров к исходящим HTTP-запросам

Добавьте пакет Microsoft.Identity.Web

Выполните следующую команду, чтобы установить пакет Microsoft.Identity.Web NuGet:

cd MyService.Web
dotnet add package Microsoft.Identity.Web

Настройка параметров Microsoft Entra

Добавьте области конфигурации Microsoft Entra и подчиненных API в MyService.Web/appsettings.json:

{
  "AzureAd": {
    "Instance": "https://login.microsoftonline.com/",
    "Domain": "<your-tenant>.onmicrosoft.com",
    "TenantId": "<tenant-guid>",
    "ClientId":  "<web-app-client-id>",
    "CallbackPath": "/signin-oidc",
    "ClientCredentials": [
      {
        "SourceType": "ClientSecret",
        "ClientSecret": "<your-client-secret>"
      }
    ]
  },
  "WeatherApi": {
    "Scopes": [ "api://<api-client-id>/.default" ]
  },
  "Logging": {
    "LogLevel":  {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*"
}

Сведения о конфигурации:

  • ClientId: идентификатор регистрации веб-приложения (а не идентификатор API)
  • ClientCredentials: учетные данные веб-приложения для получения токенов. Поддерживает несколько типов учетных данных. См. Общие сведения об учетных данных для вариантов, готовых к использованию.
  • Scopes: должен соответствовать URI идентификатора приложения API с суффиксом /.default

Предупреждение

Для продакшн используйте сертификаты или управляемое удостоверение вместо учетных данных клиента. Ознакомьтесь с рекомендациями по проверке подлинности без сертификатов .

Обновите Program.cs для веб-приложения

Замените содержимое MyService.Web/Program.cs следующим кодом, чтобы настроить проверку подлинности OIDC, получение маркера и подчиненный клиент API.

using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.Identity.Abstractions;
using Microsoft.Identity.Web;
using MyService.Web;
using MyService.Web.Components;

var builder = WebApplication.CreateBuilder(args);

builder.AddServiceDefaults();

// Authentication + Microsoft Identity Web
builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
    .AddMicrosoftIdentityWebApp(builder.Configuration.GetSection("AzureAd"))
    .EnableTokenAcquisitionToCallDownstreamApi()
    .AddInMemoryTokenCaches();

builder.Services.AddCascadingAuthenticationState();

// Blazor components
builder.Services.AddRazorComponents().AddInteractiveServerComponents();

// Blazor authentication challenge handler for incremental consent and Conditional Access
builder.Services.AddScoped<BlazorAuthenticationChallengeHandler>();

builder.Services.AddOutputCache();

// Downstream API client with MicrosoftIdentityMessageHandler
builder.Services.AddHttpClient<WeatherApiClient>(client =>
{
    // Aspire service discovery: resolves "apiservice" at runtime
    client.BaseAddress = new("https+http://apiservice");
})
.AddMicrosoftIdentityMessageHandler(builder.Configuration.GetSection("WeatherApi"));

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error", createScopeForErrors: true);
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseAuthentication();
app.UseAuthorization();
app.UseAntiforgery();
app.UseOutputCache();

app.MapStaticAssets();
app.MapRazorComponents<App>()
   .AddInteractiveServerRenderMode();

// Login/Logout endpoints with incremental consent support
app.MapGroup("/authentication").MapLoginAndLogout();

app.MapDefaultEndpoints();
app.Run();

Основные моменты:

  • AddMicrosoftIdentityWebApp: Настраивает проверку подлинности OIDC
  • EnableTokenAcquisitionToCallDownstreamApi: включает функцию получения токена для дочерних API
  • AddScoped<BlazorAuthenticationChallengeHandler>: обрабатывает добавочное согласие и условный доступ на сервере Blazor
  • AddMicrosoftIdentityMessageHandler: автоматически присоединяет маркеры носителя к запросам HttpClient
  • https+http://apiservice: обнаружение службы aspire разрешает это на фактический URL-адрес API
  • Порядок промежуточного ПО: UseAuthentication()UseAuthorization() → конечные точки

Расширение AddMicrosoftIdentityMessageHandler поддерживает несколько шаблонов конфигурации:

Вариант 1. Конфигурация из appsettings.json (показана ранее)

.AddMicrosoftIdentityMessageHandler(builder.Configuration.GetSection("WeatherApi"));

Вариант 2. Встроенная конфигурация с делегатом действия

.AddMicrosoftIdentityMessageHandler(options =>
{
    options.Scopes.Add("api://<api-client-id>/.default");
});

Вариант 3. Конфигурация каждого запроса (без параметров)

.AddMicrosoftIdentityMessageHandler();

// Then in your service, configure per-request:
var request = new HttpRequestMessage(HttpMethod.Get, "/weatherforecast")
    .WithAuthenticationOptions(options =>
    {
        options.Scopes.Add("api://<api-client-id>/.default");
    });
var response = await _httpClient.SendAsync(request);

Добавление компонентов пользовательского интерфейса Blazor

Это важно

Этот шаг часто забылся. Без компонента UserInfo пользователи не могут войти в систему.

BlazorAuthenticationChallengeHandler и LoginLogoutEndpointRouteBuilderExtensions входят в состав Microsoft.Identity.Web v3.3.0+. Они автоматически доступны после ссылки на пакет — копирование файлов не требуется.

Создание MyService.Web/Components/UserInfo.razor:

@using Microsoft.AspNetCore.Components.Authorization

<AuthorizeView>
    <Authorized>
        <span class="nav-item">Hello, @context.User.Identity?.Name</span>
        <form action="/authentication/logout" method="post" class="nav-item">
            <AntiforgeryToken />
            <input type="hidden" name="returnUrl" value="/" />
            <button type="submit" class="btn btn-link nav-link">Logout</button>
        </form>
    </Authorized>
    <NotAuthorized>
        <a href="/authentication/login?returnUrl=/" class="nav-link">Login</a>
    </NotAuthorized>
</AuthorizeView>

Добавьте в макет: Включите <UserInfo /> в ваш MainLayout.razor:

@inherits LayoutComponentBase

<div class="page">
    <div class="sidebar">
        <NavMenu />
    </div>

    <main>
        <div class="top-row px-4">
            <UserInfo />
        </div>

        <article class="content px-4">
            @Body
        </article>
    </main>
</div>

Обновление Routes.razor для AuthorizeRouteView

Замените RouteView на AuthorizeRouteView в Components/Routes.razor:

@using Microsoft.AspNetCore.Components.Authorization

<Router AppAssembly="typeof(Program).Assembly">
    <Found Context="routeData">
        <AuthorizeRouteView RouteData="routeData" DefaultLayout="typeof(Layout.MainLayout)">
            <NotAuthorized>
                <p>You are not authorized to view this page.</p>
                <a href="/authentication/login">Login</a>
            </NotAuthorized>
        </AuthorizeRouteView>
        <FocusOnNavigate RouteData="routeData" Selector="h1" />
    </Found>
</Router>

Обработка исключений на страницах, вызывающих API

Blazor Server требует явной обработки исключений для условного доступа и согласия. Необходимо обрабатывать MicrosoftIdentityWebChallengeUserException на каждой странице, которая вызывает downstream API, если ваше приложение не было предварительно санкционировано, и если вы предварительно запрашиваете все необходимые области Program.cs.

В следующем Weather.razor примере показана правильная обработка исключений:

@page "/weather"
@attribute [Authorize]

@using Microsoft.AspNetCore.Authorization
@using Microsoft.Identity.Web

@inject WeatherApiClient WeatherApi
@inject BlazorAuthenticationChallengeHandler ChallengeHandler

<PageTitle>Weather</PageTitle>

<h1>Weather</h1>

@if (!string.IsNullOrEmpty(errorMessage))
{
    <div class="alert alert-warning">@errorMessage</div>
}
else if (forecasts == null)
{
    <p><em>Loading...</em></p>
}
else
{
    <table class="table">
        <thead>
            <tr>
                <th>Date</th>
                <th>Temp. (C)</th>
                <th>Summary</th>
            </tr>
        </thead>
        <tbody>
            @foreach (var forecast in forecasts)
            {
                <tr>
                    <td>@forecast.Date.ToShortDateString()</td>
                    <td>@forecast.TemperatureC</td>
                    <td>@forecast.Summary</td>
                </tr>
            }
        </tbody>
    </table>
}

@code {
    private WeatherForecast[]? forecasts;
    private string? errorMessage;

    protected override async Task OnInitializedAsync()
    {
        if (!await ChallengeHandler.IsAuthenticatedAsync())
        {
            await ChallengeHandler.ChallengeUserWithConfiguredScopesAsync("WeatherApi:Scopes");
            return;
        }

        try
        {
            forecasts = await WeatherApi.GetWeatherAsync();
        }
        catch (Exception ex)
        {
            // Handle incremental consent / Conditional Access
            if (!await ChallengeHandler.HandleExceptionAsync(ex))
            {
                errorMessage = $"Error loading weather data: {ex.Message}";
            }
        }
    }
}

Шаблон работает следующим образом:

  1. IsAuthenticatedAsync() Проверяет, выполнил ли пользователь вход перед вызовами API.
  2. HandleExceptionAsync() перехватывает MicrosoftIdentityWebChallengeUserException (или как внутреннее исключение - InnerException).
  3. Если это исключение типа challenge, пользователь перенаправляется для повторной аутентификации с необходимыми претензиями или сферами.
  4. Если это не вызванное исключение, HandleExceptionAsync возвращает false, чтобы вы могли обрабатывать ошибку самостоятельно.

Хранение секрета клиента в секретах пользователей

Используйте диспетчер секретов .NET для безопасного хранения секрета клиента во время разработки.

Предостережение

Никогда не фиксируйте конфиденциальные данные в системе контроля версий.

Инициализация секретов пользователей и хранение секрета клиента:

cd MyService.Web
dotnet user-secrets init
dotnet user-secrets set "AzureAd:ClientCredentials:0:ClientSecret" "<your-client-secret>"

Затем обновите, appsettings.json чтобы удалить жестко закодированный секрет:

{
  "AzureAd": {
    "ClientCredentials": [
      {
        "SourceType": "ClientSecret"
      }
    ]
  }
}

Microsoft. Identity.Web поддерживает несколько типов учетных данных. Сведения о производстве см. в обзоре учетных данных.


Проверка реализации

Используйте этот контрольный список, чтобы подтвердить выполнение всех необходимых действий.

Проект API

  • [ ] Добавлен пакет Microsoft.Identity.Web
  • [ ] Обновлено appsettings.json с помощью AzureAd раздела
  • [ ] Обновлено Program.cs с помощью AddMicrosoftIdentityWebApi
  • [ ] Добавлено .RequireAuthorization() для защищенных конечных точек

Проект Web/Blazor

  • [ ] Добавлен пакет Microsoft.Identity.Web (версия 3.3.0+)
  • [ ] Обновлено appsettings.json вместе с разделами AzureAd и WeatherApi
  • [ ] Обновлено Program.cs, включая OIDC и получение токена
  • [ ] Добавлено AddScoped<BlazorAuthenticationChallengeHandler>()
  • [ ] Создано Components/UserInfo.razor (кнопка входа)
  • [ ] Обновлено MainLayout.razor для включения <UserInfo />
  • [ ] Обновлено Routes.razor с помощью AuthorizeRouteView
  • [ ] Добавлен try/catch на каждой странице, вызывающей API-интерфейсы с ChallengeHandler
  • [ ] Сохраненный секрет клиента в секретах пользователя

Проверка

  • [ ] dotnet build успешно
  • [ ] Регистрация приложений, созданные в центре администрирования Microsoft Entra
  • [ ] appsettings.json имеет реальные идентификаторы GUID (без заполнителей)

Тестирование и устранение неполадок

После завершения реализации запустите приложение и проверьте сквозной поток проверки подлинности.

Запуск приложения

Запустите Приложение Aspire AppHost, чтобы запустить как проекты веб-, так и API:

# From solution root
dotnet restore
dotnet build

# Launch AppHost (starts both Web and API)
dotnet run --project .\MyService.AppHost\MyService.AppHost.csproj

Проверка потока проверки подлинности

  1. Откройте браузер и перейдите к веб-интерфейсу Blazor (посмотрите URL-адрес на панели управления Aspire).
  2. Выберите Login → войдите с помощью Microsoft Entra.
  3. Перейдите на страницу "Погода ".
  4. Проверьте нагрузку данных о погоде (из защищенного API).

Устранение распространенных проблем

В следующей таблице перечислены частые проблемы и их решения.

Проблема Решение
401 для вызовов API Убедитесь, что области в appsettings.json соответствуют URI идентификатора приложения API
Сбой перенаправления OIDC Добавьте /signin-oidc в URI перенаправления Microsoft Entra
Токен не подключен Убедитесь, что AddMicrosoftIdentityMessageHandler вызывается на HttpClient
Сбой обнаружения служб Проверьте, что AppHost.cs ссылается на оба проекта и что они работают.
AADSTS65001 Требуется согласие администратора— предоставление согласия в Центр администрирования Microsoft Entra
Кнопка входа отсутствует Убедитесь, что UserInfo.razor существует и включен в MainLayout.razor
Цикл согласия Убедитесь, что try/catch с HandleExceptionAsync есть на всех страницах, вызывающих API.

Включение ведения журнала MSAL

При устранении неполадок проверки подлинности включите подробное журналирование MSAL, чтобы просмотреть сведения о приобретении токенов. Добавьте следующие уровни журнала в appsettings.json:

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning",
      "Microsoft.Identity": "Debug",
      "Microsoft.IdentityModel": "Debug"
    }
  }
}

Предупреждение

Отключите ведение журнала отладки в рабочей среде, так как это может быть очень подробно.

Проверка токенов

Чтобы отладить проблемы с маркерами, декодируйте JWT на jwt.ms и проверьте:

  • aud (аудитория): соответствует идентификатору клиента API или URI идентификатора приложения
  • iss (издатель): соответствует арендатору (https://login.microsoftonline.com/<tenant-id>/v2.0)
  • scp (области применения): содержат необходимые области применения
  • exp (срок действия): срок действия маркера не истек

Изучение распространенных сценариев

В следующих разделах показано, как расширить базовую реализацию для дополнительных вариантов использования.

Защита страниц Blazor

[Authorize] Добавьте атрибут на страницы, для которых требуется проверка подлинности:

@page "/weather"
@attribute [Authorize]

Или определите политики авторизации в Program.cs:

// Program.cs
builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("AdminOnly", policy => policy.RequireRole("Admin"));
});
@attribute [Authorize(Policy = "AdminOnly")]

Проверка областей в API

Убедитесь, что API принимает только маркеры с определенными областями путем RequireScopeцепочки:

app.MapGet("/weatherforecast", () =>
{
    // ... implementation
})
.RequireAuthorization()
.RequireScope("access_as_user");

Использование токенов только для приложений (межсервисное взаимодействие)

Для сценариев управляющей программы или вызовов между службами без контекста пользователя установите значение RequestAppTokentrue:

builder.Services.AddHttpClient<WeatherApiClient>(client =>
{
    client.BaseAddress = new("https+http://apiservice");
})
.AddMicrosoftIdentityMessageHandler(options =>
{
    options.Scopes.Add("api://<api-client-id>/.default");
    options.RequestAppToken = true;
});

Использование учетных данных без сертификатов в производственной среде

Для рабочих развертываний в Azure используйте управляемое удостоверение вместо секретов клиента. ClientCredentials Настройте раздел следующим образом:

{
  "AzureAd": {
    "Instance": "https://login.microsoftonline.com/",
    "TenantId": "<tenant-guid>",
    "ClientId":  "<web-app-client-id>",
    "ClientCredentials": [
      {
        "SourceType": "SignedAssertionFromManagedIdentity",
        "ManagedIdentityClientId": "<user-assigned-mi-client-id>"
      }
    ]
  }
}

Дополнительные сведения см. в разделе "Проверка подлинности без сертификатов".

Вызов подчиненных API из API (от имени)

Если вашему API нужно вызвать другой подчиненный API от имени пользователя, включите получение токена от его имени в Program.cs.

// MyService.ApiService/Program.cs
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddMicrosoftIdentityWebApi(builder.Configuration.GetSection("AzureAd"))
    .EnableTokenAcquisitionToCallDownstreamApi()
    .AddInMemoryTokenCaches();

builder.Services.AddDownstreamApi("GraphApi", builder.Configuration.GetSection("GraphApi"));

Добавьте конфигурацию нижестоящего API в appsettings.json:

{
  "GraphApi": {
    "BaseUrl": "https://graph.microsoft.com/v1.0",
    "Scopes": [ "User.Read" ]
  }
}

Затем вызовите дочерний API из конечной точки:

{
    var user = await downstreamApi.GetForUserAsync<JsonElement>("GraphApi", "me");
    return user;
}).RequireAuthorization();

Дополнительные сведения см. в разделе "Вызов нижестоящих API".