ASP.NET Core Blazor WebAssembly のセキュリティに関するその他のシナリオ

送信要求にトークンを添付する

AuthorizationMessageHandler は、送信 HttpResponseMessage インスタンスにアクセス トークンをアタッチするために使用される DelegatingHandler です。 トークンは、フレームワークによって登録されている IAccessTokenProvider サービスを使用して取得されます。 トークンを取得できない場合は、AccessTokenNotAvailableException がスローされます。 AccessTokenNotAvailableException には、Redirect メソッドがあります。このメソッドを使用すると、ユーザーを ID プロバイダーに移動して、新しいトークンを取得できます。

便宜上、フレームワークには、アプリのベース アドレスを承認された URL として使用して事前構成が行われた BaseAddressAuthorizationMessageHandler が用意されています。 アクセス トークンは、要求 URI がアプリのベース URI 内にある場合にのみ追加されます。 送信要求 URI がアプリのベース URI 内にない場合は、カスタム AuthorizationMessageHandler クラス (推奨) を使用するか、または AuthorizationMessageHandler を構成します。

注意

サーバー API アクセス用のクライアント アプリ構成に加えて、クライアントとサーバーが同じベース アドレス内に存在しない場合にクロスオリジン要求 (CORS) がサーバー API によって許可される必要があります。 サーバー側の CORS 構成の詳細については、この記事で後述する「クロスオリジン リソース共有 (CORS)」セクションを参照してください。

次の例では

using System.Net.Http;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;

...

// AddHttpClient is an extension in Microsoft.Http.Extensions
builder.Services.AddHttpClient("WebAPI", 
        client => client.BaseAddress = new Uri("https://www.example.com/base"))
    .AddHttpMessageHandler<BaseAddressAuthorizationMessageHandler>();

builder.Services.AddScoped(sp => sp.GetRequiredService<IHttpClientFactory>()
    .CreateClient("WebAPI"));

Blazor WebAssembly プロジェクト テンプレートに基づいてホストされている Blazor ソリューションの場合、既定では、要求 URI はアプリのベース URI 内にあります。 したがって、IWebAssemblyHostEnvironment.BaseAddress (new Uri(builder.HostEnvironment.BaseAddress)) は、プロジェクト テンプレートから生成されたアプリ内の HttpClient.BaseAddress に割り当てられます。

構成された HttpClient を使用し、try-catch パターンを使用して、承認された要求を行います。

@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
@inject HttpClient Http

...

protected override async Task OnInitializedAsync()
{
    private ExampleType[] examples;

    try
    {
        examples = 
            await Http.GetFromJsonAsync<ExampleType[]>("ExampleAPIMethod");

        ...
    }
    catch (AccessTokenNotAvailableException exception)
    {
        exception.Redirect();
    }
}

カスタム AuthorizationMessageHandler クラス

このセクションのこのガイダンスは、アプリのベース URI 内に存在しない URI に対して送信要求を行うクライアント アプリに推奨されます。

次の例では、カスタム クラスによって、AuthorizationMessageHandler が、HttpClient 用の DelegatingHandler として使用するために拡張されます。 ConfigureHandler によって、アクセス トークンを使用して送信 HTTP 要求を承認できるように、このハンドラーが構成されます。 アクセス トークンがアタッチされるのは、承認された URL の少なくとも 1 つが要求 URI (HttpRequestMessage.RequestUri) のベースである場合に限られます。

using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;

public class CustomAuthorizationMessageHandler : AuthorizationMessageHandler
{
    public CustomAuthorizationMessageHandler(IAccessTokenProvider provider, 
        NavigationManager navigationManager)
        : base(provider, navigationManager)
    {
        ConfigureHandler(
            authorizedUrls: new[] { "https://www.example.com/base" },
            scopes: new[] { "example.read", "example.write" });
    }
}

Program.cs では、CustomAuthorizationMessageHandler がスコープされたサービスとして登録され、名前付き HttpClient によって作成された送信 HttpResponseMessage インスタンスの DelegatingHandler として構成されます。

builder.Services.AddScoped<CustomAuthorizationMessageHandler>();

// AddHttpClient is an extension in Microsoft.Http.Extensions
builder.Services.AddHttpClient("WebAPI",
        client => client.BaseAddress = new Uri("https://www.example.com/base"))
    .AddHttpMessageHandler<CustomAuthorizationMessageHandler>();

Blazor WebAssembly プロジェクト テンプレートに基づいてホストされている Blazor ソリューションの場合、既定では、IWebAssemblyHostEnvironment.BaseAddress (new Uri(builder.HostEnvironment.BaseAddress)) が HttpClient.BaseAddress に割り当てられます。

構成が行われた HttpClient を使用し、try-catch パターンを使用して、承認された要求を行います。 CreateClient (Microsoft.Extensions.Http パッケージ) を使用してクライアントが作成される場合にサーバー API への要求を行うと、アクセス トークンが含まれるインスタンスが HttpClient に提供されます。 要求 URI が次の例 (ExampleAPIMethod) のように相対 URI である場合、それは、クライアント アプリから要求があったときに BaseAddress に結合されます。

@inject IHttpClientFactory ClientFactory

...

@code {
    private ExampleType[] examples;

    protected override async Task OnInitializedAsync()
    {
        try
        {
            var client = ClientFactory.CreateClient("WebAPI");

            examples = 
                await client.GetFromJsonAsync<ExampleType[]>("ExampleAPIMethod");
        }
        catch (AccessTokenNotAvailableException exception)
        {
            exception.Redirect();
        }
    }
}

AuthorizationMessageHandler を構成する

ConfigureHandler メソッドを使用すれば、承認された URL、スコープ、および戻り先 URL で AuthorizationMessageHandler を構成できます。 ConfigureHandler によって、アクセス トークンを使用して送信 HTTP 要求を承認できるように、ハンドラーが構成されます。 アクセス トークンがアタッチされるのは、承認された URL の少なくとも 1 つが要求 URI (HttpRequestMessage.RequestUri) のベースである場合に限られます。 要求 URI が相対 URI である場合、これは BaseAddress に結合されます。

次の例では、Program.cs で、AuthorizationMessageHandler によって HttpClient が構成されます。

using System.Net.Http;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;

...

builder.Services.AddScoped(sp => new HttpClient(
    sp.GetRequiredService<AuthorizationMessageHandler>()
    .ConfigureHandler(
        authorizedUrls: new[] { "https://www.example.com/base" },
        scopes: new[] { "example.read", "example.write" }))
    {
        BaseAddress = new Uri("https://www.example.com/base")
    });

Blazor WebAssembly プロジェクト テンプレートに基づいてホストされている Blazor ソリューションの場合、既定では、IWebAssemblyHostEnvironment.BaseAddress が以下に割り当てられます。

型指定された HttpClient

単一クラス内のすべての HTTP およびトークンの取得に関する問題を処理する、型指定されたクライアントを定義できます。

WeatherForecastClient.cs:

using System.Net.Http;
using System.Net.Http.Json;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
using static {APP ASSEMBLY}.Data;

public class WeatherForecastClient
{
    private readonly HttpClient http;

    public WeatherForecastClient(HttpClient http)
    {
        this.http = http;
    }

    public async Task<WeatherForecast[]> GetForecastAsync()
    {
        var forecasts = new WeatherForecast[0];

        try
        {
            forecasts = await http.GetFromJsonAsync<WeatherForecast[]>(
                "WeatherForecast");
        }
        catch (AccessTokenNotAvailableException exception)
        {
            exception.Redirect();
        }

        return forecasts;
    }
}

プレースホルダー {APP ASSEMBLY} は、アプリのアセンブリ名です (例: using static BlazorSample.Data;)。

Program.csの場合:

using System.Net.Http;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;

...

// AddHttpClient is an extension in Microsoft.Http.Extensions
builder.Services.AddHttpClient<WeatherForecastClient>(
        client => client.BaseAddress = new Uri("https://www.example.com/base"))
    .AddHttpMessageHandler<BaseAddressAuthorizationMessageHandler>();

Blazor WebAssembly プロジェクト テンプレートに基づいてホストされている Blazor ソリューションの場合、既定では、IWebAssemblyHostEnvironment.BaseAddress (new Uri(builder.HostEnvironment.BaseAddress)) が HttpClient.BaseAddress に割り当てられます。

FetchData コンポーネント (Pages/FetchData.razor):

@inject WeatherForecastClient Client

...

protected override async Task OnInitializedAsync()
{
    forecasts = await Client.GetForecastAsync();
}

HttpClient ハンドラーを構成する

ハンドラーは、送信 HTTP 要求の ConfigureHandler を使用して、さらに構成を行うことができます。

Program.csの場合:

// AddHttpClient is an extension in Microsoft.Http.Extensions
builder.Services.AddHttpClient<WeatherForecastClient>(
        client => client.BaseAddress = new Uri("https://www.example.com/base"))
    .AddHttpMessageHandler(sp => sp.GetRequiredService<AuthorizationMessageHandler>()
    .ConfigureHandler(
        authorizedUrls: new [] { "https://www.example.com/base" },
        scopes: new[] { "example.read", "example.write" }));

Blazor WebAssembly プロジェクト テンプレートに基づいてホストされている Blazor ソリューションの場合、既定では、IWebAssemblyHostEnvironment.BaseAddress が以下に割り当てられます。

セキュリティで保護された既定のクライアントを使用する、アプリ内の認証または承認されていない Web API 要求

通常、Blazor WebAssembly アプリがセキュリティで保護された既定の HttpClient を使用する場合、アプリでは、名前付きの HttpClient の構成を行うことで、認証または承認されていない Web API 要求が行われます。

Program.csの場合:

// AddHttpClient is an extension in Microsoft.Http.Extensions
builder.Services.AddHttpClient("WebAPI.NoAuthenticationClient", 
    client => client.BaseAddress = new Uri("https://www.example.com/base"));

Blazor WebAssembly プロジェクト テンプレートに基づいてホストされている Blazor ソリューションの場合、既定では、IWebAssemblyHostEnvironment.BaseAddress (new Uri(builder.HostEnvironment.BaseAddress)) が HttpClient.BaseAddress に割り当てられます。

前述の登録は、セキュリティで保護された既定の HttpClient 登録に追加されます。

コンポーネントでは、IHttpClientFactory (Microsoft.Extensions.Http パッケージ) から HttpClient が作成され、認証または承認されていない要求が行われます。

@inject IHttpClientFactory ClientFactory

...

@code {
    private WeatherForecast[] forecasts;

    protected override async Task OnInitializedAsync()
    {
        var client = ClientFactory.CreateClient("WebAPI.NoAuthenticationClient");

        forecasts = await client.GetFromJsonAsync<WeatherForecast[]>(
            "WeatherForecastNoAuthentication");
    }
}

注意

サーバー API のコントローラー (前の例では WeatherForecastNoAuthenticationController) では、[Authorize] 属性を使用してマークされることはありません。

既定の HttpClient インスタンスとしてセキュリティで保護されたクライアントを使用するか、セキュリティで保護されていないクライアントを使用するかは、開発者が決定します。 この決定を行う方法の 1 つは、アプリが通信する認証済みのエンドポイントと認証されていないエンドポイントの数を考慮することです。 アプリの要求の大部分が API エンドポイントをセキュリティで保護する場合は、認証された HttpClient インスタンスを既定として使用します。 それ以外の場合は、認証されていない HttpClient インスタンスを既定として登録します。

IHttpClientFactory を使用する別の方法として、匿名エンドポイントへの認証されていないアクセス用に、型指定されたクライアントを作成することもできます。

追加のアクセス トークンを要求する

アクセス トークンは、IAccessTokenProvider.RequestAccessToken を呼び出して手動で取得できます。 次の例では、既定の HttpClient に対して、アプリが追加のスコープを必要としています。 Microsoft Authentication Library (MSAL) の例では、MsalProviderOptions を使用してスコープが構成されます。

Program.csの場合:

builder.Services.AddMsalAuthentication(options =>
{
    ...

    options.ProviderOptions.AdditionalScopesToConsent.Add("{CUSTOM SCOPE 1}");
    options.ProviderOptions.AdditionalScopesToConsent.Add("{CUSTOM SCOPE 2}");
}

前の例の {CUSTOM SCOPE 1}{CUSTOM SCOPE 2} のプレースホルダーは、カスタム スコープです。

IAccessTokenProvider.RequestToken メソッドには、指定されたスコープ セットを使用して、アプリでアクセス トークンをプロビジョニングできるようにするオーバーロードが用意されています。

Razor コンポーネントでは、次のようになります。

@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
@inject IAccessTokenProvider TokenProvider

...

var tokenResult = await TokenProvider.RequestAccessToken(
    new AccessTokenRequestOptions
    {
        Scopes = new[] { "{CUSTOM SCOPE 1}", "{CUSTOM SCOPE 2}" }
    });

if (tokenResult.TryGetToken(out var token))
{
    ...
}

前の例の {CUSTOM SCOPE 1}{CUSTOM SCOPE 2} のプレースホルダーは、カスタム スコープです。

AccessTokenResult.TryGetToken が次のように返します。

  • token を使用する場合は true
  • トークンが取得されない場合は false

クロスオリジン リソース共有 (CORS)

CORS 要求で資格情報 (承認 cookie またはヘッダー) を送信するときには、CORS ポリシーで Authorization ヘッダーが許可されている必要があります。

次のポリシーには、以下についての構成が含まれています。

  • 要求元 (http://localhost:5000https://localhost:5001)。
  • 任意のメソッド (動詞)。
  • Content-Type ヘッダーと Authorization ヘッダー。 カスタム ヘッダー (x-custom-headerなど) を許可するには、WithHeaders の呼び出し時にヘッダーを一覧表示します。
  • クライアント側の JavaScript コードによって設定された資格情報 (credentials プロパティが includeに設定されています)。
app.UseCors(policy => 
    policy.WithOrigins("http://localhost:5000", "https://localhost:5001")
    .AllowAnyMethod()
    .WithHeaders(HeaderNames.ContentType, HeaderNames.Authorization, "x-custom-header")
    .AllowCredentials());

Blazor WebAssembly プロジェクト テンプレートに基づいてホストされている Blazor ソリューションを使用すると、クライアント アプリとサーバー アプリに同じベース アドレスが使用されます。 クライアント アプリの HttpClient.BaseAddress は、既定では builder.HostEnvironment.BaseAddress の URI に設定されます。 ホストされている Blazor ソリューションの既定の構成では、CORS 構成は必須では ありません。 サーバー プロジェクトでホストされておらず、サーバー アプリのベース アドレスを共有していない追加のクライアント アプリでは、サーバー プロジェクト内の CORS 構成は 必須です

詳細については、「ASP.NET Core でクロスオリジン要求 (CORS) を有効にする」と、サンプル アプリの HTTP 要求テスター コンポーネント (Components/HTTPRequestTester.razor) を参照してください。

トークン要求エラーを処理する

シングル ページ アプリケーション (SPA) で OpenID Connect (OIDC) を使用してユーザーが認証されると、ユーザーが資格情報を入力したときに設定されるセッション cookie の形式で、SPA 内および Identity プロバイダー (IP) 内で、認証状態がローカルに維持されます。

通常、IP によってユーザーに出力されるトークンが有効なのは短時間のため (通常は約 1 時間)、クライアント アプリでは定期的に新しいトークンをフェッチする必要があります。 それ以外の場合は、許可されたトークンの有効期限が切れると、ユーザーがログアウトします。 ほとんどの場合、OIDC クライアントでは、ユーザーに対して認証の再要求を行うことなく、新しいトークンをプロビジョニングできます。これは、認証状態または IP 内に保持される "セッション" によるものです。

場合によっては、ユーザーの介入なしに、クライアントでトークンを取得できないことがあります。たとえば、何らかの理由でユーザーが明示的に IP からログアウトした場合などです。 このシナリオは、ユーザーが https://login.microsoftonline.com にアクセスしてログアウトした場合に発生します。これらのシナリオでは、ユーザーがログアウトしたことを、アプリはすぐに認識しません。クライアントで保持されるトークンは、有効でなくなった可能性があります。 また、クライアントでは、現在のトークンの有効期限が切れた後に、ユーザーの介入なしに新しいトークンをプロビジョニングすることはできません。

これらのシナリオは、トークンベースの認証に固有のものではありません。 これらは、SPA の性質の一部です。 また、認証 cookie が削除されると、cookie を使用する SPA でサーバー API を呼び出すこともできません。

保護されたリソースに対する API 呼び出しをアプリで実行するときは、次の点に注意する必要があります。

  • API を呼び出すための新しいアクセス トークンをプロビジョニングするには、ユーザーの再認証が必要です。
  • 有効かもしれないトークンがクライアントにある場合でも、そのトークンはユーザーによって取り消されているため、サーバーへの呼び出しが失敗する可能性があります。

アプリでトークンが要求されている場合は、次の 2 つの結果が考えられます。

  • 要求が成功します。アプリには有効なトークンがあります。
  • 要求が失敗します。新しいトークンを取得するために、ユーザーの再認証が必要となります。

トークン要求が失敗した場合は、リダイレクトを実行する前に、現在の状態を保存するかどうかを決定する必要があります。 次のようないくつかの方法がありますが、さらに複雑になります。

  • 現在のページの状態をセッション ストレージに格納します。 OnInitializedAsync ライフサイクル メソッド (OnInitializedAsync) 中に、続行する前に状態を復元できるかどうかを確認します。
  • クエリ文字列パラメーターを追加して、以前に保存した状態を再ハイドレートする必要があることをアプリに通知する方法として使用します。
  • 他の項目と競合するリスクなしにセッション ストレージにデータを格納するための一意識別子を持つクエリ文字列パラメーターを追加します。

以下の例では、次のことを行っています。

  • ログイン ページにリダイレクトする前に、状態を保持します。
  • クエリ文字列パラメーターを使用して、認証後に以前の状態を回復します。
...
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
@inject IAccessTokenProvider TokenProvider
...

<EditForm Model="User" @onsubmit="OnSaveAsync">
    <label>User
        <InputText @bind-Value="User.Name" />
    </label>
    <label>Last name
        <InputText @bind-Value="User.LastName" />
    </label>
</EditForm>

@code {
    public class Profile
    {
        public string Name { get; set; }
        public string LastName { get; set; }
    }

    public Profile User { get; set; } = new Profile();

    protected override async Task OnInitializedAsync()
    {
        var currentQuery = new Uri(Navigation.Uri).Query;

        if (currentQuery.Contains("state=resumeSavingProfile"))
        {
            User = await JS.InvokeAsync<Profile>("sessionStorage.getState", 
                "resumeSavingProfile");
        }
    }

    public async Task OnSaveAsync()
    {
        var http = new HttpClient();
        http.BaseAddress = new Uri(Navigation.BaseUri);

        var resumeUri = Navigation.Uri + $"?state=resumeSavingProfile";

        var tokenResult = await TokenProvider.RequestAccessToken(
            new AccessTokenRequestOptions
            {
                ReturnUrl = resumeUri
            });

        if (tokenResult.TryGetToken(out var token))
        {
            http.DefaultRequestHeaders.Add("Authorization", 
                $"Bearer {token.Value}");
            await http.PostAsJsonAsync("Save", User);
        }
        else
        {
            await JS.InvokeVoidAsync("sessionStorage.setState", 
                "resumeSavingProfile", User);
            Navigation.NavigateTo(tokenResult.RedirectUrl);
        }
    }
}

認証操作の前にアプリの状態を保存する

認証操作中に、ブラウザーが IP にリダイレクトされる前に、アプリの状態を保存することが必要になる場合があります。 状態コンテナーを使用していて、認証が成功した後に状態を復元する場合には、このようなことが起こる可能性があります。 カスタム認証状態オブジェクトを使用して、アプリ固有の状態、またはその参照を保持し、認証操作が正常に完了した後で、その状態を復元することができます。 このアプローチの例を次に示します。

状態コンテナー クラスは、アプリの状態値を保持するプロパティを使用して、アプリ内に作成されます。 次の例では、コンテナーを使用して、既定の Blazor プロジェクト テンプレートCounter コンポーネント (Pages/Counter.razor) のカウンター値を維持します。 コンテナーをシリアル化および逆シリアル化するためのメソッドは、System.Text.Json に基づいています。

using System.Text.Json;

public class StateContainer
{
    public int CounterValue { get; set; }

    public string GetStateForLocalStorage()
    {
        return JsonSerializer.Serialize(this);
    }

    public void SetStateFromLocalStorage(string locallyStoredState)
    {
        var deserializedState = 
            JsonSerializer.Deserialize<StateContainer>(locallyStoredState);

        CounterValue = deserializedState.CounterValue;
    }
}

Counter コンポーネントでは、状態コンテナーを使用して、コンポーネントの外部に currentCount 値を維持します。

@page "/counter"
@inject StateContainer State

<h1>Counter</h1>

<p>Current count: @currentCount</p>

<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>

@code {
    private int currentCount = 0;

    protected override void OnInitialized()
    {
        if (State.CounterValue > 0)
        {
            currentCount = State.CounterValue;
        }
    }

    private void IncrementCount()
    {
        currentCount++;
        State.CounterValue = currentCount;
    }
}

RemoteAuthenticationState から ApplicationAuthenticationState を作成します。 ローカルに格納されている状態の識別子として機能する Id プロパティを指定します。

ApplicationAuthenticationState.cs:

using Microsoft.AspNetCore.Components.WebAssembly.Authentication;

public class ApplicationAuthenticationState : RemoteAuthenticationState
{
    public string Id { get; set; }
}

Authentication コンポーネント (Pages/Authentication.razor) では、StateContainer のシリアル化と逆シリアル化の方法である GetStateForLocalStorage および SetStateFromLocalStorage で、ローカル セッション ストレージを使用してアプリの状態を保存および復元します。

@page "/authentication/{action}"
@inject IJSRuntime JS
@inject StateContainer State
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication

<RemoteAuthenticatorViewCore Action="@Action"
                             TAuthenticationState="ApplicationAuthenticationState"
                             AuthenticationState="AuthenticationState"
                             OnLogInSucceeded="RestoreState"
                             OnLogOutSucceeded="RestoreState" />

@code {
    [Parameter]
    public string Action { get; set; }

    public ApplicationAuthenticationState AuthenticationState { get; set; } =
        new ApplicationAuthenticationState();

    protected override async Task OnInitializedAsync()
    {
        if (RemoteAuthenticationActions.IsAction(RemoteAuthenticationActions.LogIn,
            Action) ||
            RemoteAuthenticationActions.IsAction(RemoteAuthenticationActions.LogOut,
            Action))
        {
            AuthenticationState.Id = Guid.NewGuid().ToString();

            await JS.InvokeVoidAsync("sessionStorage.setItem",
                AuthenticationState.Id, State.GetStateForLocalStorage());
        }
    }

    private async Task RestoreState(ApplicationAuthenticationState state)
    {
        if (state.Id != null)
        {
            var locallyStoredState = await JS.InvokeAsync<string>(
                "sessionStorage.getItem", state.Id);

            if (locallyStoredState != null)
            {
                State.SetStateFromLocalStorage(locallyStoredState);
                await JS.InvokeVoidAsync("sessionStorage.removeItem", state.Id);
            }
        }
    }
}

この例では、Azure Active Directory (AAD) を使用して認証を行います。 Program.csの場合:

  • ApplicationAuthenticationState は、Microsoft Authentication Library (MSAL) の RemoteAuthenticationState 型として、構成が行われます。
  • 状態コンテナーがサービス コンテナーに登録されます。
builder.Services.AddMsalAuthentication<ApplicationAuthenticationState>(options =>
{
    builder.Configuration.Bind("AzureAd", options.ProviderOptions.Authentication);
});

builder.Services.AddSingleton<StateContainer>();

アプリ ルートをカスタマイズする

Microsoft.AspNetCore.Components.WebAssembly.Authentication ライブラリでは既定で、次の表に示すルートを使用して、さまざまな認証状態が表されます。

ルート 目的
authentication/login サインイン操作をトリガーします。
authentication/login-callback サインイン操作の結果を処理します。
authentication/login-failed 何らかの理由でサインイン操作が失敗した場合に、エラー メッセージを表示します。
authentication/logout サインアウト操作をトリガーします。
authentication/logout-callback サインアウト操作の結果を処理します。
authentication/logout-failed 何らかの理由でサインアウト操作が失敗した場合に、エラー メッセージを表示します。
authentication/logged-out ユーザーが正常にログアウトしたことを示します。
authentication/profile ユーザー プロファイルを編集する操作をトリガーします。
authentication/register 新しいユーザーを登録する操作をトリガーします。

上の表に示すルートは、RemoteAuthenticationOptions<TRemoteAuthenticationProviderOptions>.AuthenticationPaths を使用して構成できます。 カスタム ルートを提供するオプションを設定する場合は、アプリに各パスを処理するルートがあることを確認します。

次の例では、すべてのパスが /security で始まります。

Authentication コンポーネント (Pages/Authentication.razor):

@page "/security/{action}"
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication

<RemoteAuthenticatorView Action="@Action" />

@code{
    [Parameter]
    public string Action { get; set; }
}

Program.csの場合:

builder.Services.AddApiAuthorization(options => { 
    options.AuthenticationPaths.LogInPath = "security/login";
    options.AuthenticationPaths.LogInCallbackPath = "security/login-callback";
    options.AuthenticationPaths.LogInFailedPath = "security/login-failed";
    options.AuthenticationPaths.LogOutPath = "security/logout";
    options.AuthenticationPaths.LogOutCallbackPath = "security/logout-callback";
    options.AuthenticationPaths.LogOutFailedPath = "security/logout-failed";
    options.AuthenticationPaths.LogOutSucceededPath = "security/logged-out";
    options.AuthenticationPaths.ProfilePath = "security/profile";
    options.AuthenticationPaths.RegisterPath = "security/register";
});

完全に異なるパスを必要とする場合は、前述のようにルートを設定し、明示的なアクション パラメーターを使用して RemoteAuthenticatorView をレンダリングします。

@page "/register"

<RemoteAuthenticatorView Action="@RemoteAuthenticationActions.Register" />

UI を別のページに分割することもできます。

認証ユーザー インターフェイスをカスタマイズする

RemoteAuthenticatorView には、各認証状態の UI 部分の既定のセットが含まれます。 各状態は、カスタム RenderFragment を渡すことでカスタマイズできます。 最初のログイン プロセス中に表示されるテキストをカスタマイズするには、次のように RemoteAuthenticatorView を変更します。

Authentication コンポーネント (Pages/Authentication.razor):

@page "/security/{action}"
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication

<RemoteAuthenticatorView Action="@Action">
    <LoggingIn>
        You are about to be redirected to https://login.microsoftonline.com.
    </LoggingIn>
</RemoteAuthenticatorView>

@code{
    [Parameter]
    public string Action { get; set; }
}

RemoteAuthenticatorView には、次の表に示す認証ルートごとに使用できるフラグメントが 1 つあります。

ルート Fragment
authentication/login <LoggingIn>
authentication/login-callback <CompletingLoggingIn>
authentication/login-failed <LogInFailed>
authentication/logout <LogOut>
authentication/logout-callback <CompletingLogOut>
authentication/logout-failed <LogOutFailed>
authentication/logged-out <LogOutSucceeded>
authentication/profile <UserProfile>
authentication/register <Registering>

ユーザーをカスタマイズする

アプリにバインドされているユーザーをカスタマイズできます。

ペイロード要求を使用してユーザーをカスタマイズする

次の例では、アプリの認証されたユーザーが、ユーザーの認証方法ごとに amr 要求を受け取ります。 この amr 要求は、Microsoft Identity プラットフォーム v1.0 ペイロード要求でトークンのサブジェクトが認証された方法を示しています。 この例では、RemoteUserAccount に基づくカスタム ユーザー アカウント クラスを使用します。

RemoteUserAccount クラスを拡張するクラスを作成します。 次の例では、AuthenticationMethod プロパティを amr JSON プロパティ値のユーザー配列に設定します。 ユーザーが認証されると、フレームワークによって AuthenticationMethod が自動的に設定されます。

using System.Text.Json.Serialization;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;

public class CustomUserAccount : RemoteUserAccount
{
    [JsonPropertyName("amr")]
    public string[] AuthenticationMethod { get; set; }
}

AccountClaimsPrincipalFactory<TAccount> を拡張するファクトリを作成して、CustomUserAccount.AuthenticationMethod に格納されているユーザーの認証方法から要求を作成します。

using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication.Internal;

public class CustomAccountFactory 
    : AccountClaimsPrincipalFactory<CustomUserAccount>
{
    public CustomAccountFactory(NavigationManager navigationManager, 
        IAccessTokenProviderAccessor accessor) : base(accessor)
    {
    }

    public override async ValueTask<ClaimsPrincipal> CreateUserAsync(
        CustomUserAccount account, RemoteAuthenticationUserOptions options)
    {
        var initialUser = await base.CreateUserAsync(account, options);

        if (initialUser.Identity.IsAuthenticated)
        {
            foreach (var value in account.AuthenticationMethod)
            {
                ((ClaimsIdentity)initialUser.Identity)
                    .AddClaim(new Claim("amr", value));
            }
        }

        return initialUser;
    }
}

使用中の認証プロバイダーの CustomAccountFactory を登録します。 次の登録のいずれかが有効です。

  • AddOidcAuthentication:

    using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
    
    ...
    
    builder.Services.AddOidcAuthentication<RemoteAuthenticationState, 
        CustomUserAccount>(options =>
        {
            ...
        })
        .AddAccountClaimsPrincipalFactory<RemoteAuthenticationState, 
            CustomUserAccount, CustomAccountFactory>();
    
  • AddMsalAuthentication:

    using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
    
    ...
    
    builder.Services.AddMsalAuthentication<RemoteAuthenticationState, 
        CustomUserAccount>(options =>
        {
            ...
        })
        .AddAccountClaimsPrincipalFactory<RemoteAuthenticationState, 
            CustomUserAccount, CustomAccountFactory>();
    
  • AddApiAuthorization:

    using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
    
    ...
    
    builder.Services.AddApiAuthorization<RemoteAuthenticationState, 
        CustomUserAccount>(options =>
        {
            ...
        })
        .AddAccountClaimsPrincipalFactory<RemoteAuthenticationState, 
            CustomUserAccount, CustomAccountFactory>();
    

カスタム ユーザー アカウント クラスを持つ AAD セキュリティ グループおよびロール

AAD セキュリティ グループと AAD 管理者ロール、およびカスタム ユーザー アカウント クラスを使用するその他の例については、Azure Active Directory のグループとロールを使用する ASP.NET Core Blazor WebAssembly を参照してください。

認証を使用したプリレンダリングのサポート

現在、認証と認可を必要とするプリレンダリング コンテンツはサポートされていません。 Blazor WebAssembly セキュリティ アプリのトピックのいずれかのガイダンスを実行した後は、この後の手順に従って次のようなアプリを作成できます。

  • 承認が不要なパスをプリレンダリングする。
  • 承認が必要なパスをプリレンダリングしない。

クライアント ( Client ) アプリの Program.cs で、共通のサービスの登録を別のメソッド (たとえば、ConfigureCommonServices) に組み入れます。 一般的なサービスは、開発者がクライアントとサーバー ( Server ) アプリの両方で使用するために登録するものです。

public static void ConfigureCommonServices(IServiceCollection services)
{
    services.Add...;
}

Program.cs:

var builder = WebAssemblyHostBuilder.CreateDefault(args);
...

builder.Services.AddScoped( ... );

ConfigureCommonServices(builder.Services);

await builder.Build().RunAsync();

サーバー アプリの Program.cs ファイルで、次の追加サービスを登録し、ConfigureCommonServices を呼び出します。

using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.AspNetCore.Components.Server;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;

...

builder.Services.AddRazorPages();
builder.Services.AddScoped<AuthenticationStateProvider, 
    ServerAuthenticationStateProvider>();
builder.Services.AddScoped<SignOutSessionStateManager>();

Client.Program.ConfigureCommonServices(services);

サーバー アプリの Program.cs で、app.MapFallbackToFile("index.html")app.MapFallbackToPage("/_Host") に置き換えます。

app.MapControllers();
app.MapFallbackToPage("/_Host");

サーバー アプリで、Pages フォルダーが存在しない場合は作成します。 サーバー アプリの Pages フォルダー内に _Host.cshtml ページを作成します。 クライアント アプリの wwwroot/index.html ファイルの内容を Pages/_Host.cshtml ファイルに貼り付けます。 ファイルの内容を更新します。

  • ファイルの先頭に、@page "_Host" を追加します。

  • <div id="app">Loading...</div> タグを次のように置き換えます。

    <div id="app">
        @if (!HttpContext.Request.Path.StartsWithSegments("/authentication"))
        {
            <component type="typeof({CLIENT APP ASSEMBLY NAME}.App)" 
                render-mode="Static" />
        }
        else
        {
            <text>Loading...</text>
        }
    </div>
    

    前の例では、プレースホルダー {CLIENT APP ASSEMBLY NAME} はクライアント アプリのアセンブリ名です (たとえば BlazorSample.Client)。

ホストされているアプリおよびサードパーティ ログイン プロバイダーに関するオプション

ホストされている Blazor WebAssembly アプリをサードパーティ プロバイダーで認証および承認する場合、ユーザーの認証にはいくつかのオプションを使用できます。 どれを選択するかは、シナリオによって異なります。

詳細については、「ASP.NET Core で外部プロバイダーからの追加の要求とトークンを保持する」を参照してください。

ユーザーを認証して保護されたサードパーティ API のみを呼び出す

サードパーティ API プロバイダーに対してクライアント側の OAuth フローを使用してユーザーを認証します。

builder.services.AddOidcAuthentication(options => { ... });

このシナリオでは:

  • アプリをホストしているサーバーは関与しません。
  • サーバー上の API を保護することはできません。
  • アプリでは、保護されたサードパーティ API のみを呼び出すことができます。

サードパーティ プロバイダーでユーザーを認証し、ホスト サーバーおよびサード パーティ上で保護された API を呼び出す

サードパーティのログイン プロバイダーを使用して、Identity の構成を行います。 サードパーティ API へのアクセスに必要なトークンを取得し、それを格納します。

ユーザーがログインすると、認証プロセスの一環として、アクセス トークンと更新トークンが IdentityID によって収集されます。 その時点で、サードパーティ API の API 呼び出しを行うために使用できる方法はいくつかあります。

サーバー アクセス トークンを使用してサードパーティのアクセス トークンを取得する

サーバー上で生成されたアクセス トークンを使用して、サーバー API エンドポイントからサードパーティのアクセストークンを取得します。 そこから、サードパーティのアクセス トークンを使用して、クライアント上の IdentityID からサードパーティ API リソースを直接呼び出します。

この方法はお勧めしません。 この方法では、サードパーティのアクセス トークンをパブリック クライアント用に生成されたものとして扱う必要があります。 OAuth 規約では、パブリック アプリにはクライアント シークレットがありません。これはシークレットを安全に格納することが信頼できないためです。アクセス トークンは機密クライアントに対して生成されます。 機密クライアントとは、クライアント シークレットを持っていてシークレットを安全に格納できると想定されるクライアントです。

  • サードパーティのアクセス トークンには、サードパーティがより信頼できるクライアントのトークンを生成したという事実に基づいて機密性の高い操作を実行するための追加のスコープが付与される場合があります。
  • 同様に、信頼されていないクライアントに更新トークンを発行してはなりません。それを行ってしまうと、他の制限が適用されない限り、クライアントは無制限にアクセスできます。

サードパーティ API を呼び出すために、クライアントからサーバー API への API 呼び出しを行う

クライアントからサーバー API への API 呼び出しを行います。 サーバーから、サードパーティ API リソースのアクセス トークンを取得し、必要な呼び出しはすべて発行します。

この方法では、サードパーティ API を呼び出すためにサーバー経由で追加のネットワーク ホップが必要になりますが、それによって最終的にはより安全なエクスペリエンスが得られます。

  • サーバーでは、更新トークンを格納し、アプリからサードパーティ リソースへのアクセスが決して失われないようにすることができます。
  • アプリでは、より機密性の高いアクセス許可を含む可能性のあるサーバーからのアクセス トークンをリークさせることはできません。

OpenID Connect (OIDC) v2.0 エンドポイントを使用する

認証ライブラリと Blazor プロジェクト テンプレートにより、OpenID Connect (OIDC) v1.0 エンドポイントが使用されます。 v2.0 エンドポイントを使用するには、JWT ベアラー JwtBearerOptions.Authority オプションの構成を行います。 次の例では、Authority プロパティに v2.0 セグメントを追加することで、v2.0 に対して AAD の構成が行われます。

builder.Services.Configure<JwtBearerOptions>(
    AzureADDefaults.JwtBearerAuthenticationScheme, 
    options =>
    {
        options.Authority += "/v2.0";
    });

または、アプリ設定ファイル (appsettings.json) で設定を行うこともできます。

{
  "Local": {
    "Authority": "https://login.microsoftonline.com/common/oauth2/v2.0/",
    ...
  }
}

証明機関へのセグメントを追跡することがアプリの OIDC プロバイダー (AAD 以外のプロバイダーなど) にとって適切でない場合は、Authority プロパティを直接設定します。 JwtBearerOptions またはアプリ設定ファイル (appsettings.json) で Authority キーを使用してプロパティを設定します。

ID トークンの要求のリストは、v2.0 エンドポイントで変更されています。 詳細については、「Microsoft ID プラットフォーム (v2.0) に更新する理由」を参照してください。

コンポーネントで gRPC を構成し、使用する

ASP.NET Core gRPC フレームワークを使用するように Blazor WebAssembly アプリを構成するには:

  • サーバーで gRPC-Web を有効にします。 詳細については、「ブラウザー アプリでの gRPC の使用」を参照してください。
  • アプリのメッセージ ハンドラーに gRPC サービスを登録します。 次の例では、gRPC チュートリアル (Program.cs) から GreeterClient サービスを使用するように、アプリの認可メッセージ ハンドラーが構成されます。
using System.Net.Http;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
using Grpc.Net.Client;
using Grpc.Net.Client.Web;
using {APP ASSEMBLY}.Shared;

...

builder.Services.AddScoped(sp =>
{
    var baseAddressMessageHandler = 
        sp.GetRequiredService<BaseAddressAuthorizationMessageHandler>();
    baseAddressMessageHandler.InnerHandler = new HttpClientHandler();
    var grpcWebHandler = 
        new GrpcWebHandler(GrpcWebMode.GrpcWeb, baseAddressMessageHandler);
    var channel = GrpcChannel.ForAddress(builder.HostEnvironment.BaseAddress, 
        new GrpcChannelOptions { HttpHandler = grpcWebHandler });

    return new Greeter.GreeterClient(channel);
});

プレースホルダー {APP ASSEMBLY} は、アプリのアセンブリ名です (例: BlazorSample)。 ホストされている Blazor ソリューションの Shared プロジェクトに .proto ファイルを置きます。

クライアント アプリのコンポーネントでは、gRPC クライアント (Pages/Grpc.razor) を使用し、gRPC 呼び出しを行うことができます。

@page "/grpc"
@using Microsoft.AspNetCore.Authorization
@using {APP ASSEMBLY}.Shared
@attribute [Authorize]
@inject Greeter.GreeterClient GreeterClient

<h1>Invoke gRPC service</h1>

<p>
    <input @bind="name" placeholder="Type your name" />
    <button @onclick="GetGreeting" class="btn btn-primary">Call gRPC service</button>
</p>

Server response: <strong>@serverResponse</strong>

@code {
    private string name = "Bert";
    private string serverResponse;

    private async Task GetGreeting()
    {
        try
        {
            var request = new HelloRequest { Name = name };
            var reply = await GreeterClient.SayHelloAsync(request);
            serverResponse = reply.Message;
        }
        catch (Grpc.Core.RpcException ex)
            when (ex.Status.DebugException is 
                AccessTokenNotAvailableException tokenEx)
        {
            tokenEx.Redirect();
        }
    }
}

プレースホルダー {APP ASSEMBLY} は、アプリのアセンブリ名です (例: BlazorSample)。 Status.DebugException プロパティを使用するには、Grpc.Net.Client バージョン 2.30.0 以降を使用します。

詳細については、「ブラウザー アプリでの gRPC の使用」を参照してください。

Authentication.MSAL JavaScript ライブラリのカスタム バージョンをビルドする

アプリでカスタム バージョンの JavaScript 用 Microsoft Authentication Library (MSAL js) が必要な場合は、次の手順を実行します。

  1. システムに最新の開発者用 .NET SDK がインストールされていることを確認するか、次から最新の開発者用 SDK を入手してインストールします: .NET Core SDK: インストーラーとバイナリ。 このシナリオでは、内部 NuGet フィードの構成は必要ありません。
  2. ソースから ASP.NET Core をビルドする」のドキュメントに従って、開発用の dotnet/aspnetcore GitHub リポジトリを設定します。 dotnet/aspnetcore GitHub リポジトリをフォークしてクローンするか、ZIP アーカイブをダウンロードします。
  3. src/Components/WebAssembly/Authentication.Msal/src/Interop/package.json ファイルを開き、必要なバージョンの @azure/msal-browser を設定します。 リリース済みバージョンの一覧については、@azure/msal-browser の npm Web サイトにアクセスし、 [Versions](バージョン) タブを選択してください。
  4. コマンド シェルで yarn build コマンドを使用して、src/Components/WebAssembly/Authentication.Msal/src フォルダー内に Authentication.Msal プロジェクトをビルドします。
  5. アプリで圧縮された資産 (Brotli/Gzip) を使用する場合は、Interop/dist/Release/AuthenticationService.js ファイルを圧縮します。
  6. AuthenticationService.js ファイルと、生成した場合はファイルの圧縮バージョン (.br/.gz) を、Interop/dist/Release フォルダーから、アプリの発行済み資産にあるアプリの publish/wwwroot/_content/Microsoft.Authentication.WebAssembly.Msal フォルダーにコピーします。

その他の技術情報

送信要求にトークンを添付する

AuthorizationMessageHandler は、送信 HttpResponseMessage インスタンスにアクセス トークンをアタッチするために使用される DelegatingHandler です。 トークンは、フレームワークによって登録されている IAccessTokenProvider サービスを使用して取得されます。 トークンを取得できない場合は、AccessTokenNotAvailableException がスローされます。 AccessTokenNotAvailableException には、Redirect メソッドがあります。このメソッドを使用すると、ユーザーを ID プロバイダーに移動して、新しいトークンを取得できます。

便宜上、フレームワークには、アプリのベース アドレスを承認された URL として使用して事前構成が行われた BaseAddressAuthorizationMessageHandler が用意されています。 アクセス トークンは、要求 URI がアプリのベース URI 内にある場合にのみ追加されます。 送信要求 URI がアプリのベース URI 内にない場合は、カスタム AuthorizationMessageHandler クラス (推奨) を使用するか、または AuthorizationMessageHandler を構成します。

注意

サーバー API アクセス用のクライアント アプリ構成に加えて、クライアントとサーバーが同じベース アドレス内に存在しない場合にクロスオリジン要求 (CORS) がサーバー API によって許可される必要があります。 サーバー側の CORS 構成の詳細については、この記事で後述する「クロスオリジン リソース共有 (CORS)」セクションを参照してください。

次の例では

using System.Net.Http;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;

...

builder.Services.AddHttpClient("WebAPI", 
        client => client.BaseAddress = new Uri("https://www.example.com/base"))
    .AddHttpMessageHandler<BaseAddressAuthorizationMessageHandler>();

builder.Services.AddScoped(sp => sp.GetRequiredService<IHttpClientFactory>()
    .CreateClient("WebAPI"));

Blazor WebAssembly プロジェクト テンプレートに基づいてホストされている Blazor ソリューションの場合、既定では、要求 URI はアプリのベース URI 内にあります。 したがって、IWebAssemblyHostEnvironment.BaseAddress (new Uri(builder.HostEnvironment.BaseAddress)) は、プロジェクト テンプレートから生成されたアプリ内の HttpClient.BaseAddress に割り当てられます。

構成された HttpClient を使用し、try-catch パターンを使用して、承認された要求を行います。

@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
@inject HttpClient Http

...

protected override async Task OnInitializedAsync()
{
    private ExampleType[] examples;

    try
    {
        examples = 
            await Http.GetFromJsonAsync<ExampleType[]>("ExampleAPIMethod");

        ...
    }
    catch (AccessTokenNotAvailableException exception)
    {
        exception.Redirect();
    }
}

カスタム AuthorizationMessageHandler クラス

このセクションのこのガイダンスは、アプリのベース URI 内に存在しない URI に対して送信要求を行うクライアント アプリに推奨されます。

次の例では、カスタム クラスによって、AuthorizationMessageHandler が、HttpClient 用の DelegatingHandler として使用するために拡張されます。 ConfigureHandler によって、アクセス トークンを使用して送信 HTTP 要求を承認できるように、このハンドラーが構成されます。 アクセス トークンがアタッチされるのは、承認された URL の少なくとも 1 つが要求 URI (HttpRequestMessage.RequestUri) のベースである場合に限られます。

using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;

public class CustomAuthorizationMessageHandler : AuthorizationMessageHandler
{
    public CustomAuthorizationMessageHandler(IAccessTokenProvider provider, 
        NavigationManager navigationManager)
        : base(provider, navigationManager)
    {
        ConfigureHandler(
            authorizedUrls: new[] { "https://www.example.com/base" },
            scopes: new[] { "example.read", "example.write" });
    }
}

Program.cs では、CustomAuthorizationMessageHandler がスコープされたサービスとして登録され、名前付き HttpClient によって作成された送信 HttpResponseMessage インスタンスの DelegatingHandler として構成されます。

builder.Services.AddScoped<CustomAuthorizationMessageHandler>();

builder.Services.AddHttpClient("WebAPI",
        client => client.BaseAddress = new Uri("https://www.example.com/base"))
    .AddHttpMessageHandler<CustomAuthorizationMessageHandler>();

Blazor WebAssembly プロジェクト テンプレートに基づいてホストされている Blazor ソリューションの場合、既定では、IWebAssemblyHostEnvironment.BaseAddress (new Uri(builder.HostEnvironment.BaseAddress)) が HttpClient.BaseAddress に割り当てられます。

構成が行われた HttpClient を使用し、try-catch パターンを使用して、承認された要求を行います。 CreateClient (Microsoft.Extensions.Http パッケージ) を使用してクライアントが作成される場合にサーバー API への要求を行うと、アクセス トークンが含まれるインスタンスが HttpClient に提供されます。 要求 URI が次の例 (ExampleAPIMethod) のように相対 URI である場合、それは、クライアント アプリから要求があったときに BaseAddress に結合されます。

@inject IHttpClientFactory ClientFactory

...

@code {
    private ExampleType[] examples;

    protected override async Task OnInitializedAsync()
    {
        try
        {
            var client = ClientFactory.CreateClient("WebAPI");

            examples = 
                await client.GetFromJsonAsync<ExampleType[]>("ExampleAPIMethod");
        }
        catch (AccessTokenNotAvailableException exception)
        {
            exception.Redirect();
        }
    }
}

AuthorizationMessageHandler を構成する

ConfigureHandler メソッドを使用すれば、承認された URL、スコープ、および戻り先 URL で AuthorizationMessageHandler を構成できます。 ConfigureHandler によって、アクセス トークンを使用して送信 HTTP 要求を承認できるように、ハンドラーが構成されます。 アクセス トークンがアタッチされるのは、承認された URL の少なくとも 1 つが要求 URI (HttpRequestMessage.RequestUri) のベースである場合に限られます。 要求 URI が相対 URI である場合、これは BaseAddress に結合されます。

次の例では、Program.cs で、AuthorizationMessageHandler によって HttpClient が構成されます。

using System.Net.Http;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;

...

builder.Services.AddScoped(sp => new HttpClient(
    sp.GetRequiredService<AuthorizationMessageHandler>()
    .ConfigureHandler(
        authorizedUrls: new[] { "https://www.example.com/base" },
        scopes: new[] { "example.read", "example.write" }))
    {
        BaseAddress = new Uri("https://www.example.com/base")
    });

Blazor WebAssembly プロジェクト テンプレートに基づいてホストされている Blazor ソリューションの場合、既定では、IWebAssemblyHostEnvironment.BaseAddress が以下に割り当てられます。

型指定された HttpClient

単一クラス内のすべての HTTP およびトークンの取得に関する問題を処理する、型指定されたクライアントを定義できます。

WeatherForecastClient.cs:

using System.Net.Http;
using System.Net.Http.Json;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
using static {APP ASSEMBLY}.Data;

public class WeatherForecastClient
{
    private readonly HttpClient http;

    public WeatherForecastClient(HttpClient http)
    {
        this.http = http;
    }

    public async Task<WeatherForecast[]> GetForecastAsync()
    {
        var forecasts = new WeatherForecast[0];

        try
        {
            forecasts = await http.GetFromJsonAsync<WeatherForecast[]>(
                "WeatherForecast");
        }
        catch (AccessTokenNotAvailableException exception)
        {
            exception.Redirect();
        }

        return forecasts;
    }
}

プレースホルダー {APP ASSEMBLY} は、アプリのアセンブリ名です (例: using static BlazorSample.Data;)。

Program.csの場合:

using System.Net.Http;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;

...

builder.Services.AddHttpClient<WeatherForecastClient>(
        client => client.BaseAddress = new Uri("https://www.example.com/base"))
    .AddHttpMessageHandler<BaseAddressAuthorizationMessageHandler>();

Blazor WebAssembly プロジェクト テンプレートに基づいてホストされている Blazor ソリューションの場合、既定では、IWebAssemblyHostEnvironment.BaseAddress (new Uri(builder.HostEnvironment.BaseAddress)) が HttpClient.BaseAddress に割り当てられます。

FetchData コンポーネント (Pages/FetchData.razor):

@inject WeatherForecastClient Client

...

protected override async Task OnInitializedAsync()
{
    forecasts = await Client.GetForecastAsync();
}

HttpClient ハンドラーを構成する

ハンドラーは、送信 HTTP 要求の ConfigureHandler を使用して、さらに構成を行うことができます。

Program.csの場合:

builder.Services.AddHttpClient<WeatherForecastClient>(
        client => client.BaseAddress = new Uri("https://www.example.com/base"))
    .AddHttpMessageHandler(sp => sp.GetRequiredService<AuthorizationMessageHandler>()
    .ConfigureHandler(
        authorizedUrls: new [] { "https://www.example.com/base" },
        scopes: new[] { "example.read", "example.write" }));

Blazor WebAssembly プロジェクト テンプレートに基づいてホストされている Blazor ソリューションの場合、既定では、IWebAssemblyHostEnvironment.BaseAddress が以下に割り当てられます。

セキュリティで保護された既定のクライアントを使用する、アプリ内の認証または承認されていない Web API 要求

通常、Blazor WebAssembly アプリがセキュリティで保護された既定の HttpClient を使用する場合、アプリでは、名前付きの HttpClient の構成を行うことで、認証または承認されていない Web API 要求が行われます。

Program.csの場合:

builder.Services.AddHttpClient("WebAPI.NoAuthenticationClient", 
    client => client.BaseAddress = new Uri("https://www.example.com/base"));

Blazor WebAssembly プロジェクト テンプレートに基づいてホストされている Blazor ソリューションの場合、既定では、IWebAssemblyHostEnvironment.BaseAddress (new Uri(builder.HostEnvironment.BaseAddress)) が HttpClient.BaseAddress に割り当てられます。

前述の登録は、セキュリティで保護された既定の HttpClient 登録に追加されます。

コンポーネントでは、IHttpClientFactory (Microsoft.Extensions.Http パッケージ) から HttpClient が作成され、認証または承認されていない要求が行われます。

@inject IHttpClientFactory ClientFactory

...

@code {
    private WeatherForecast[] forecasts;

    protected override async Task OnInitializedAsync()
    {
        var client = ClientFactory.CreateClient("WebAPI.NoAuthenticationClient");

        forecasts = await client.GetFromJsonAsync<WeatherForecast[]>(
            "WeatherForecastNoAuthentication");
    }
}

注意

サーバー API のコントローラー (前の例では WeatherForecastNoAuthenticationController) では、[Authorize] 属性を使用してマークされることはありません。

既定の HttpClient インスタンスとしてセキュリティで保護されたクライアントを使用するか、セキュリティで保護されていないクライアントを使用するかは、開発者が決定します。 この決定を行う方法の 1 つは、アプリが通信する認証済みのエンドポイントと認証されていないエンドポイントの数を考慮することです。 アプリの要求の大部分が API エンドポイントをセキュリティで保護する場合は、認証された HttpClient インスタンスを既定として使用します。 それ以外の場合は、認証されていない HttpClient インスタンスを既定として登録します。

IHttpClientFactory を使用する別の方法として、匿名エンドポイントへの認証されていないアクセス用に、型指定されたクライアントを作成することもできます。

追加のアクセス トークンを要求する

アクセス トークンは、IAccessTokenProvider.RequestAccessToken を呼び出して手動で取得できます。 次の例では、既定の HttpClient に対して、アプリが追加のスコープを必要としています。 Microsoft Authentication Library (MSAL) の例では、MsalProviderOptions を使用してスコープが構成されます。

Program.csの場合:

builder.Services.AddMsalAuthentication(options =>
{
    ...

    options.ProviderOptions.AdditionalScopesToConsent.Add("{CUSTOM SCOPE 1}");
    options.ProviderOptions.AdditionalScopesToConsent.Add("{CUSTOM SCOPE 2}");
}

前の例の {CUSTOM SCOPE 1}{CUSTOM SCOPE 2} のプレースホルダーは、カスタム スコープです。

IAccessTokenProvider.RequestToken メソッドには、指定されたスコープ セットを使用して、アプリでアクセス トークンをプロビジョニングできるようにするオーバーロードが用意されています。

Razor コンポーネントでは、次のようになります。

@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
@inject IAccessTokenProvider TokenProvider

...

var tokenResult = await TokenProvider.RequestAccessToken(
    new AccessTokenRequestOptions
    {
        Scopes = new[] { "{CUSTOM SCOPE 1}", "{CUSTOM SCOPE 2}" }
    });

if (tokenResult.TryGetToken(out var token))
{
    ...
}

前の例の {CUSTOM SCOPE 1}{CUSTOM SCOPE 2} のプレースホルダーは、カスタム スコープです。

AccessTokenResult.TryGetToken が次のように返します。

  • token を使用する場合は true
  • トークンが取得されない場合は false

クロスオリジン リソース共有 (CORS)

CORS 要求で資格情報 (承認 cookie またはヘッダー) を送信するときには、CORS ポリシーで Authorization ヘッダーが許可されている必要があります。

次のポリシーには、以下についての構成が含まれています。

  • 要求元 (http://localhost:5000https://localhost:5001)。
  • 任意のメソッド (動詞)。
  • Content-Type ヘッダーと Authorization ヘッダー。 カスタム ヘッダー (x-custom-headerなど) を許可するには、WithHeaders の呼び出し時にヘッダーを一覧表示します。
  • クライアント側の JavaScript コードによって設定された資格情報 (credentials プロパティが includeに設定されています)。
app.UseCors(policy => 
    policy.WithOrigins("http://localhost:5000", "https://localhost:5001")
    .AllowAnyMethod()
    .WithHeaders(HeaderNames.ContentType, HeaderNames.Authorization, "x-custom-header")
    .AllowCredentials());

Blazor WebAssembly プロジェクト テンプレートに基づいてホストされている Blazor ソリューションを使用すると、クライアント アプリとサーバー アプリに同じベース アドレスが使用されます。 クライアント アプリの HttpClient.BaseAddress は、既定では builder.HostEnvironment.BaseAddress の URI に設定されます。 ホストされている Blazor ソリューションの既定の構成では、CORS 構成は必須では ありません。 サーバー プロジェクトでホストされておらず、サーバー アプリのベース アドレスを共有していない追加のクライアント アプリでは、サーバー プロジェクト内の CORS 構成は 必須です

詳細については、「ASP.NET Core でクロスオリジン要求 (CORS) を有効にする」と、サンプル アプリの HTTP 要求テスター コンポーネント (Components/HTTPRequestTester.razor) を参照してください。

トークン要求エラーを処理する

シングル ページ アプリケーション (SPA) で OpenID Connect (OIDC) を使用してユーザーが認証されると、ユーザーが資格情報を入力したときに設定されるセッション cookie の形式で、SPA 内および Identity プロバイダー (IP) 内で、認証状態がローカルに維持されます。

通常、IP によってユーザーに出力されるトークンが有効なのは短時間のため (通常は約 1 時間)、クライアント アプリでは定期的に新しいトークンをフェッチする必要があります。 それ以外の場合は、許可されたトークンの有効期限が切れると、ユーザーがログアウトします。 ほとんどの場合、OIDC クライアントでは、ユーザーに対して認証の再要求を行うことなく、新しいトークンをプロビジョニングできます。これは、認証状態または IP 内に保持される "セッション" によるものです。

場合によっては、ユーザーの介入なしに、クライアントでトークンを取得できないことがあります。たとえば、何らかの理由でユーザーが明示的に IP からログアウトした場合などです。 このシナリオは、ユーザーが https://login.microsoftonline.com にアクセスしてログアウトした場合に発生します。これらのシナリオでは、ユーザーがログアウトしたことを、アプリはすぐに認識しません。クライアントで保持されるトークンは、有効でなくなった可能性があります。 また、クライアントでは、現在のトークンの有効期限が切れた後に、ユーザーの介入なしに新しいトークンをプロビジョニングすることはできません。

これらのシナリオは、トークンベースの認証に固有のものではありません。 これらは、SPA の性質の一部です。 また、認証 cookie が削除されると、cookie を使用する SPA でサーバー API を呼び出すこともできません。

保護されたリソースに対する API 呼び出しをアプリで実行するときは、次の点に注意する必要があります。

  • API を呼び出すための新しいアクセス トークンをプロビジョニングするには、ユーザーの再認証が必要です。
  • 有効かもしれないトークンがクライアントにある場合でも、そのトークンはユーザーによって取り消されているため、サーバーへの呼び出しが失敗する可能性があります。

アプリでトークンが要求されている場合は、次の 2 つの結果が考えられます。

  • 要求が成功します。アプリには有効なトークンがあります。
  • 要求が失敗します。新しいトークンを取得するために、ユーザーの再認証が必要となります。

トークン要求が失敗した場合は、リダイレクトを実行する前に、現在の状態を保存するかどうかを決定する必要があります。 次のようないくつかの方法がありますが、さらに複雑になります。

  • 現在のページの状態をセッション ストレージに格納します。 OnInitializedAsync ライフサイクル メソッド (OnInitializedAsync) 中に、続行する前に状態を復元できるかどうかを確認します。
  • クエリ文字列パラメーターを追加して、以前に保存した状態を再ハイドレートする必要があることをアプリに通知する方法として使用します。
  • 他の項目と競合するリスクなしにセッション ストレージにデータを格納するための一意識別子を持つクエリ文字列パラメーターを追加します。

以下の例では、次のことを行っています。

  • ログイン ページにリダイレクトする前に、状態を保持します。
  • クエリ文字列パラメーターを使用して、認証後に以前の状態を回復します。
...
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
@inject IAccessTokenProvider TokenProvider
...

<EditForm Model="User" @onsubmit="OnSaveAsync">
    <label>User
        <InputText @bind-Value="User.Name" />
    </label>
    <label>Last name
        <InputText @bind-Value="User.LastName" />
    </label>
</EditForm>

@code {
    public class Profile
    {
        public string Name { get; set; }
        public string LastName { get; set; }
    }

    public Profile User { get; set; } = new Profile();

    protected override async Task OnInitializedAsync()
    {
        var currentQuery = new Uri(Navigation.Uri).Query;

        if (currentQuery.Contains("state=resumeSavingProfile"))
        {
            User = await JS.InvokeAsync<Profile>("sessionStorage.getState", 
                "resumeSavingProfile");
        }
    }

    public async Task OnSaveAsync()
    {
        var http = new HttpClient();
        http.BaseAddress = new Uri(Navigation.BaseUri);

        var resumeUri = Navigation.Uri + $"?state=resumeSavingProfile";

        var tokenResult = await TokenProvider.RequestAccessToken(
            new AccessTokenRequestOptions
            {
                ReturnUrl = resumeUri
            });

        if (tokenResult.TryGetToken(out var token))
        {
            http.DefaultRequestHeaders.Add("Authorization", 
                $"Bearer {token.Value}");
            await http.PostAsJsonAsync("Save", User);
        }
        else
        {
            await JS.InvokeVoidAsync("sessionStorage.setState", 
                "resumeSavingProfile", User);
            Navigation.NavigateTo(tokenResult.RedirectUrl);
        }
    }
}

認証操作の前にアプリの状態を保存する

認証操作中に、ブラウザーが IP にリダイレクトされる前に、アプリの状態を保存することが必要になる場合があります。 状態コンテナーを使用していて、認証が成功した後に状態を復元する場合には、このようなことが起こる可能性があります。 カスタム認証状態オブジェクトを使用して、アプリ固有の状態、またはその参照を保持し、認証操作が正常に完了した後で、その状態を復元することができます。 このアプローチの例を次に示します。

状態コンテナー クラスは、アプリの状態値を保持するプロパティを使用して、アプリ内に作成されます。 次の例では、コンテナーを使用して、既定の Blazor プロジェクト テンプレートCounter コンポーネント (Pages/Counter.razor) のカウンター値を維持します。 コンテナーをシリアル化および逆シリアル化するためのメソッドは、System.Text.Json に基づいています。

using System.Text.Json;

public class StateContainer
{
    public int CounterValue { get; set; }

    public string GetStateForLocalStorage()
    {
        return JsonSerializer.Serialize(this);
    }

    public void SetStateFromLocalStorage(string locallyStoredState)
    {
        var deserializedState = 
            JsonSerializer.Deserialize<StateContainer>(locallyStoredState);

        CounterValue = deserializedState.CounterValue;
    }
}

Counter コンポーネントでは、状態コンテナーを使用して、コンポーネントの外部に currentCount 値を維持します。

@page "/counter"
@inject StateContainer State

<h1>Counter</h1>

<p>Current count: @currentCount</p>

<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>

@code {
    private int currentCount = 0;

    protected override void OnInitialized()
    {
        if (State.CounterValue > 0)
        {
            currentCount = State.CounterValue;
        }
    }

    private void IncrementCount()
    {
        currentCount++;
        State.CounterValue = currentCount;
    }
}

RemoteAuthenticationState から ApplicationAuthenticationState を作成します。 ローカルに格納されている状態の識別子として機能する Id プロパティを指定します。

ApplicationAuthenticationState.cs:

using Microsoft.AspNetCore.Components.WebAssembly.Authentication;

public class ApplicationAuthenticationState : RemoteAuthenticationState
{
    public string Id { get; set; }
}

Authentication コンポーネント (Pages/Authentication.razor) では、StateContainer のシリアル化と逆シリアル化の方法である GetStateForLocalStorage および SetStateFromLocalStorage で、ローカル セッション ストレージを使用してアプリの状態を保存および復元します。

@page "/authentication/{action}"
@inject IJSRuntime JS
@inject StateContainer State
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication

<RemoteAuthenticatorViewCore Action="@Action"
                             TAuthenticationState="ApplicationAuthenticationState"
                             AuthenticationState="AuthenticationState"
                             OnLogInSucceeded="RestoreState"
                             OnLogOutSucceeded="RestoreState" />

@code {
    [Parameter]
    public string Action { get; set; }

    public ApplicationAuthenticationState AuthenticationState { get; set; } =
        new ApplicationAuthenticationState();

    protected override async Task OnInitializedAsync()
    {
        if (RemoteAuthenticationActions.IsAction(RemoteAuthenticationActions.LogIn,
            Action) ||
            RemoteAuthenticationActions.IsAction(RemoteAuthenticationActions.LogOut,
            Action))
        {
            AuthenticationState.Id = Guid.NewGuid().ToString();

            await JS.InvokeVoidAsync("sessionStorage.setItem",
                AuthenticationState.Id, State.GetStateForLocalStorage());
        }
    }

    private async Task RestoreState(ApplicationAuthenticationState state)
    {
        if (state.Id != null)
        {
            var locallyStoredState = await JS.InvokeAsync<string>(
                "sessionStorage.getItem", state.Id);

            if (locallyStoredState != null)
            {
                State.SetStateFromLocalStorage(locallyStoredState);
                await JS.InvokeVoidAsync("sessionStorage.removeItem", state.Id);
            }
        }
    }
}

この例では、Azure Active Directory (AAD) を使用して認証を行います。 Program.csの場合:

  • ApplicationAuthenticationState は、Microsoft Authentication Library (MSAL) の RemoteAuthenticationState 型として、構成が行われます。
  • 状態コンテナーがサービス コンテナーに登録されます。
builder.Services.AddMsalAuthentication<ApplicationAuthenticationState>(options =>
{
    builder.Configuration.Bind("AzureAd", options.ProviderOptions.Authentication);
});

builder.Services.AddSingleton<StateContainer>();

アプリ ルートをカスタマイズする

Microsoft.AspNetCore.Components.WebAssembly.Authentication ライブラリでは既定で、次の表に示すルートを使用して、さまざまな認証状態が表されます。

ルート 目的
authentication/login サインイン操作をトリガーします。
authentication/login-callback サインイン操作の結果を処理します。
authentication/login-failed 何らかの理由でサインイン操作が失敗した場合に、エラー メッセージを表示します。
authentication/logout サインアウト操作をトリガーします。
authentication/logout-callback サインアウト操作の結果を処理します。
authentication/logout-failed 何らかの理由でサインアウト操作が失敗した場合に、エラー メッセージを表示します。
authentication/logged-out ユーザーが正常にログアウトしたことを示します。
authentication/profile ユーザー プロファイルを編集する操作をトリガーします。
authentication/register 新しいユーザーを登録する操作をトリガーします。

上の表に示すルートは、RemoteAuthenticationOptions<TRemoteAuthenticationProviderOptions>.AuthenticationPaths を使用して構成できます。 カスタム ルートを提供するオプションを設定する場合は、アプリに各パスを処理するルートがあることを確認します。

次の例では、すべてのパスが /security で始まります。

Authentication コンポーネント (Pages/Authentication.razor):

@page "/security/{action}"
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication

<RemoteAuthenticatorView Action="@Action" />

@code{
    [Parameter]
    public string Action { get; set; }
}

Program.csの場合:

builder.Services.AddApiAuthorization(options => { 
    options.AuthenticationPaths.LogInPath = "security/login";
    options.AuthenticationPaths.LogInCallbackPath = "security/login-callback";
    options.AuthenticationPaths.LogInFailedPath = "security/login-failed";
    options.AuthenticationPaths.LogOutPath = "security/logout";
    options.AuthenticationPaths.LogOutCallbackPath = "security/logout-callback";
    options.AuthenticationPaths.LogOutFailedPath = "security/logout-failed";
    options.AuthenticationPaths.LogOutSucceededPath = "security/logged-out";
    options.AuthenticationPaths.ProfilePath = "security/profile";
    options.AuthenticationPaths.RegisterPath = "security/register";
});

完全に異なるパスを必要とする場合は、前述のようにルートを設定し、明示的なアクション パラメーターを使用して RemoteAuthenticatorView をレンダリングします。

@page "/register"

<RemoteAuthenticatorView Action="@RemoteAuthenticationActions.Register" />

UI を別のページに分割することもできます。

認証ユーザー インターフェイスをカスタマイズする

RemoteAuthenticatorView には、各認証状態の UI 部分の既定のセットが含まれます。 各状態は、カスタム RenderFragment を渡すことでカスタマイズできます。 最初のログイン プロセス中に表示されるテキストをカスタマイズするには、次のように RemoteAuthenticatorView を変更します。

Authentication コンポーネント (Pages/Authentication.razor):

@page "/security/{action}"
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication

<RemoteAuthenticatorView Action="@Action">
    <LoggingIn>
        You are about to be redirected to https://login.microsoftonline.com.
    </LoggingIn>
</RemoteAuthenticatorView>

@code{
    [Parameter]
    public string Action { get; set; }
}

RemoteAuthenticatorView には、次の表に示す認証ルートごとに使用できるフラグメントが 1 つあります。

ルート Fragment
authentication/login <LoggingIn>
authentication/login-callback <CompletingLoggingIn>
authentication/login-failed <LogInFailed>
authentication/logout <LogOut>
authentication/logout-callback <CompletingLogOut>
authentication/logout-failed <LogOutFailed>
authentication/logged-out <LogOutSucceeded>
authentication/profile <UserProfile>
authentication/register <Registering>

ユーザーをカスタマイズする

アプリにバインドされているユーザーをカスタマイズできます。

ペイロード要求を使用してユーザーをカスタマイズする

次の例では、アプリの認証されたユーザーが、ユーザーの認証方法ごとに amr 要求を受け取ります。 この amr 要求は、Microsoft Identity プラットフォーム v1.0 ペイロード要求でトークンのサブジェクトが認証された方法を示しています。 この例では、RemoteUserAccount に基づくカスタム ユーザー アカウント クラスを使用します。

RemoteUserAccount クラスを拡張するクラスを作成します。 次の例では、AuthenticationMethod プロパティを amr JSON プロパティ値のユーザー配列に設定します。 ユーザーが認証されると、フレームワークによって AuthenticationMethod が自動的に設定されます。

using System.Text.Json.Serialization;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;

public class CustomUserAccount : RemoteUserAccount
{
    [JsonPropertyName("amr")]
    public string[] AuthenticationMethod { get; set; }
}

AccountClaimsPrincipalFactory<TAccount> を拡張するファクトリを作成して、CustomUserAccount.AuthenticationMethod に格納されているユーザーの認証方法から要求を作成します。

using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication.Internal;

public class CustomAccountFactory 
    : AccountClaimsPrincipalFactory<CustomUserAccount>
{
    public CustomAccountFactory(NavigationManager navigationManager, 
        IAccessTokenProviderAccessor accessor) : base(accessor)
    {
    }

    public override async ValueTask<ClaimsPrincipal> CreateUserAsync(
        CustomUserAccount account, RemoteAuthenticationUserOptions options)
    {
        var initialUser = await base.CreateUserAsync(account, options);

        if (initialUser.Identity.IsAuthenticated)
        {
            foreach (var value in account.AuthenticationMethod)
            {
                ((ClaimsIdentity)initialUser.Identity)
                    .AddClaim(new Claim("amr", value));
            }
        }

        return initialUser;
    }
}

使用中の認証プロバイダーの CustomAccountFactory を登録します。 次の登録のいずれかが有効です。

  • AddOidcAuthentication:

    using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
    
    ...
    
    builder.Services.AddOidcAuthentication<RemoteAuthenticationState, 
        CustomUserAccount>(options =>
        {
            ...
        })
        .AddAccountClaimsPrincipalFactory<RemoteAuthenticationState, 
            CustomUserAccount, CustomAccountFactory>();
    
  • AddMsalAuthentication:

    using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
    
    ...
    
    builder.Services.AddMsalAuthentication<RemoteAuthenticationState, 
        CustomUserAccount>(options =>
        {
            ...
        })
        .AddAccountClaimsPrincipalFactory<RemoteAuthenticationState, 
            CustomUserAccount, CustomAccountFactory>();
    
  • AddApiAuthorization:

    using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
    
    ...
    
    builder.Services.AddApiAuthorization<RemoteAuthenticationState, 
        CustomUserAccount>(options =>
        {
            ...
        })
        .AddAccountClaimsPrincipalFactory<RemoteAuthenticationState, 
            CustomUserAccount, CustomAccountFactory>();
    

カスタム ユーザー アカウント クラスを持つ AAD セキュリティ グループおよびロール

AAD セキュリティ グループと AAD 管理者ロール、およびカスタム ユーザー アカウント クラスを使用するその他の例については、Azure Active Directory のグループとロールを使用する ASP.NET Core Blazor WebAssembly を参照してください。

認証を使用したプリレンダリングのサポート

現在、認証と認可を必要とするプリレンダリング コンテンツはサポートされていません。 Blazor WebAssembly セキュリティ アプリのトピックのいずれかのガイダンスを実行した後は、この後の手順に従って次のようなアプリを作成できます。

  • 承認が不要なパスをプリレンダリングする。
  • 承認が必要なパスをプリレンダリングしない。

クライアント ( Client ) アプリの Program クラス (Program.cs) で、共通のサービスの登録を別のメソッド (たとえば、ConfigureCommonServices) に組み入れます。 一般的なサービスは、開発者がクライアントとサーバー ( Server ) アプリの両方で使用するために登録するものです。

public class Program
{
    public static async Task Main(string[] args)
    {
        var builder = WebAssemblyHostBuilder.CreateDefault(args);
        builder.RootComponents.Add...;

        // Services that only the client app uses
        builder.Services.AddScoped( ... );

        ConfigureCommonServices(builder.Services);

        await builder.Build().RunAsync();
    }

    public static void ConfigureCommonServices(IServiceCollection services)
    {
        // Common service registrations that both apps use
        services.Add...;
    }
}

サーバー アプリの Startup.ConfigureServices で、次の追加サービスを登録し、ConfigureCommonServices を呼び出します。

using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.AspNetCore.Components.Server;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;

public void ConfigureServices(IServiceCollection services)
{
    ...

    services.AddRazorPages();
    services.AddScoped<AuthenticationStateProvider, 
        ServerAuthenticationStateProvider>();
    services.AddScoped<SignOutSessionStateManager>();

    Client.Program.ConfigureCommonServices(services);
}

サーバー アプリの Startup.Configure メソッドで、endpoints.MapFallbackToFile("index.html")endpoints.MapFallbackToPage("/_Host") に置き換えます。

app.UseEndpoints(endpoints =>
{
    endpoints.MapControllers();
    endpoints.MapFallbackToPage("/_Host");
});

サーバー アプリで、Pages フォルダーが存在しない場合は作成します。 サーバー アプリの Pages フォルダー内に _Host.cshtml ページを作成します。 クライアント アプリの wwwroot/index.html ファイルの内容を Pages/_Host.cshtml ファイルに貼り付けます。 ファイルの内容を更新します。

  • ファイルの先頭に、@page "_Host" を追加します。

  • <div id="app">Loading...</div> タグを次のように置き換えます。

    <div id="app">
        @if (!HttpContext.Request.Path.StartsWithSegments("/authentication"))
        {
            <component type="typeof({CLIENT APP ASSEMBLY NAME}.App)" 
                render-mode="Static" />
        }
        else
        {
            <text>Loading...</text>
        }
    </div>
    

    前の例では、プレースホルダー {CLIENT APP ASSEMBLY NAME} はクライアント アプリのアセンブリ名です (たとえば BlazorSample.Client)。

ホストされているアプリおよびサードパーティ ログイン プロバイダーに関するオプション

ホストされている Blazor WebAssembly アプリをサードパーティ プロバイダーで認証および承認する場合、ユーザーの認証にはいくつかのオプションを使用できます。 どれを選択するかは、シナリオによって異なります。

詳細については、「ASP.NET Core で外部プロバイダーからの追加の要求とトークンを保持する」を参照してください。

ユーザーを認証して保護されたサードパーティ API のみを呼び出す

サードパーティ API プロバイダーに対してクライアント側の OAuth フローを使用してユーザーを認証します。

builder.services.AddOidcAuthentication(options => { ... });

このシナリオでは:

  • アプリをホストしているサーバーは関与しません。
  • サーバー上の API を保護することはできません。
  • アプリでは、保護されたサードパーティ API のみを呼び出すことができます。

サードパーティ プロバイダーでユーザーを認証し、ホスト サーバーおよびサード パーティ上で保護された API を呼び出す

サードパーティのログイン プロバイダーを使用して、Identity の構成を行います。 サードパーティ API へのアクセスに必要なトークンを取得し、それを格納します。

ユーザーがログインすると、認証プロセスの一環として、アクセス トークンと更新トークンが IdentityID によって収集されます。 その時点で、サードパーティ API の API 呼び出しを行うために使用できる方法はいくつかあります。

サーバー アクセス トークンを使用してサードパーティのアクセス トークンを取得する

サーバー上で生成されたアクセス トークンを使用して、サーバー API エンドポイントからサードパーティのアクセストークンを取得します。 そこから、サードパーティのアクセス トークンを使用して、クライアント上の IdentityID からサードパーティ API リソースを直接呼び出します。

この方法はお勧めしません。 この方法では、サードパーティのアクセス トークンをパブリック クライアント用に生成されたものとして扱う必要があります。 OAuth 規約では、パブリック アプリにはクライアント シークレットがありません。これはシークレットを安全に格納することが信頼できないためです。アクセス トークンは機密クライアントに対して生成されます。 機密クライアントとは、クライアント シークレットを持っていてシークレットを安全に格納できると想定されるクライアントです。

  • サードパーティのアクセス トークンには、サードパーティがより信頼できるクライアントのトークンを生成したという事実に基づいて機密性の高い操作を実行するための追加のスコープが付与される場合があります。
  • 同様に、信頼されていないクライアントに更新トークンを発行してはなりません。それを行ってしまうと、他の制限が適用されない限り、クライアントは無制限にアクセスできます。

サードパーティ API を呼び出すために、クライアントからサーバー API への API 呼び出しを行う

クライアントからサーバー API への API 呼び出しを行います。 サーバーから、サードパーティ API リソースのアクセス トークンを取得し、必要な呼び出しはすべて発行します。

この方法では、サードパーティ API を呼び出すためにサーバー経由で追加のネットワーク ホップが必要になりますが、それによって最終的にはより安全なエクスペリエンスが得られます。

  • サーバーでは、更新トークンを格納し、アプリからサードパーティ リソースへのアクセスが決して失われないようにすることができます。
  • アプリでは、より機密性の高いアクセス許可を含む可能性のあるサーバーからのアクセス トークンをリークさせることはできません。

OpenID Connect (OIDC) v2.0 エンドポイントを使用する

認証ライブラリと Blazor プロジェクト テンプレートにより、OpenID Connect (OIDC) v1.0 エンドポイントが使用されます。 v2.0 エンドポイントを使用するには、JWT ベアラー JwtBearerOptions.Authority オプションの構成を行います。 次の例では、Authority プロパティに v2.0 セグメントを追加することで、v2.0 に対して AAD の構成が行われます。

builder.Services.Configure<JwtBearerOptions>(
    AzureADDefaults.JwtBearerAuthenticationScheme, 
    options =>
    {
        options.Authority += "/v2.0";
    });

または、アプリ設定ファイル (appsettings.json) で設定を行うこともできます。

{
  "Local": {
    "Authority": "https://login.microsoftonline.com/common/oauth2/v2.0/",
    ...
  }
}

証明機関へのセグメントを追跡することがアプリの OIDC プロバイダー (AAD 以外のプロバイダーなど) にとって適切でない場合は、Authority プロパティを直接設定します。 JwtBearerOptions またはアプリ設定ファイル (appsettings.json) で Authority キーを使用してプロパティを設定します。

ID トークンの要求のリストは、v2.0 エンドポイントで変更されています。 詳細については、「Microsoft ID プラットフォーム (v2.0) に更新する理由」を参照してください。

コンポーネントで gRPC を構成し、使用する

ASP.NET Core gRPC フレームワークを使用するように Blazor WebAssembly アプリを構成するには:

  • サーバーで gRPC-Web を有効にします。 詳細については、「ブラウザー アプリでの gRPC の使用」を参照してください。
  • アプリのメッセージ ハンドラーに gRPC サービスを登録します。 次の例では、gRPC チュートリアル (Program.cs) から GreeterClient サービスを使用するように、アプリの認可メッセージ ハンドラーが構成されます。
using System.Net.Http;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
using Grpc.Net.Client;
using Grpc.Net.Client.Web;
using {APP ASSEMBLY}.Shared;

...

builder.Services.AddScoped(sp =>
{
    var baseAddressMessageHandler = 
        sp.GetRequiredService<BaseAddressAuthorizationMessageHandler>();
    baseAddressMessageHandler.InnerHandler = new HttpClientHandler();
    var grpcWebHandler = 
        new GrpcWebHandler(GrpcWebMode.GrpcWeb, baseAddressMessageHandler);
    var channel = GrpcChannel.ForAddress(builder.HostEnvironment.BaseAddress, 
        new GrpcChannelOptions { HttpHandler = grpcWebHandler });

    return new Greeter.GreeterClient(channel);
});

プレースホルダー {APP ASSEMBLY} は、アプリのアセンブリ名です (例: BlazorSample)。 ホストされている Blazor ソリューションの Shared プロジェクトに .proto ファイルを置きます。

クライアント アプリのコンポーネントでは、gRPC クライアント (Pages/Grpc.razor) を使用し、gRPC 呼び出しを行うことができます。

@page "/grpc"
@using Microsoft.AspNetCore.Authorization
@using {APP ASSEMBLY}.Shared
@attribute [Authorize]
@inject Greeter.GreeterClient GreeterClient

<h1>Invoke gRPC service</h1>

<p>
    <input @bind="name" placeholder="Type your name" />
    <button @onclick="GetGreeting" class="btn btn-primary">Call gRPC service</button>
</p>

Server response: <strong>@serverResponse</strong>

@code {
    private string name = "Bert";
    private string serverResponse;

    private async Task GetGreeting()
    {
        try
        {
            var request = new HelloRequest { Name = name };
            var reply = await GreeterClient.SayHelloAsync(request);
            serverResponse = reply.Message;
        }
        catch (Grpc.Core.RpcException ex)
            when (ex.Status.DebugException is 
                AccessTokenNotAvailableException tokenEx)
        {
            tokenEx.Redirect();
        }
    }
}

プレースホルダー {APP ASSEMBLY} は、アプリのアセンブリ名です (例: BlazorSample)。 Status.DebugException プロパティを使用するには、Grpc.Net.Client バージョン 2.30.0 以降を使用します。

詳細については、「ブラウザー アプリでの gRPC の使用」を参照してください。

Authentication.MSAL JavaScript ライブラリのカスタム バージョンをビルドする

アプリでカスタム バージョンの JavaScript 用 Microsoft Authentication Library (MSAL js) が必要な場合は、次の手順を実行します。

  1. システムに最新の開発者用 .NET SDK がインストールされていることを確認するか、次から最新の開発者用 SDK を入手してインストールします: .NET Core SDK: インストーラーとバイナリ。 このシナリオでは、内部 NuGet フィードの構成は必要ありません。
  2. ソースから ASP.NET Core をビルドする」のドキュメントに従って、開発用の dotnet/aspnetcore GitHub リポジトリを設定します。 dotnet/aspnetcore GitHub リポジトリをフォークしてクローンするか、ZIP アーカイブをダウンロードします。
  3. src/Components/WebAssembly/Authentication.Msal/src/Interop/package.json ファイルを開き、必要なバージョンの @azure/msal-browser を設定します。 リリース済みバージョンの一覧については、@azure/msal-browser の npm Web サイトにアクセスし、 [Versions](バージョン) タブを選択してください。
  4. コマンド シェルで yarn build コマンドを使用して、src/Components/WebAssembly/Authentication.Msal/src フォルダー内に Authentication.Msal プロジェクトをビルドします。
  5. アプリで圧縮された資産 (Brotli/Gzip) を使用する場合は、Interop/dist/Release/AuthenticationService.js ファイルを圧縮します。
  6. AuthenticationService.js ファイルと、生成した場合はファイルの圧縮バージョン (.br/.gz) を、Interop/dist/Release フォルダーから、アプリの発行済み資産にあるアプリの publish/wwwroot/_content/Microsoft.Authentication.WebAssembly.Msal フォルダーにコピーします。

その他の技術情報

送信要求にトークンを添付する

AuthorizationMessageHandler は、送信 HttpResponseMessage インスタンスにアクセス トークンをアタッチするために使用される DelegatingHandler です。 トークンは、フレームワークによって登録されている IAccessTokenProvider サービスを使用して取得されます。 トークンを取得できない場合は、AccessTokenNotAvailableException がスローされます。 AccessTokenNotAvailableException には、Redirect メソッドがあります。このメソッドを使用すると、ユーザーを ID プロバイダーに移動して、新しいトークンを取得できます。

便宜上、フレームワークには、アプリのベース アドレスを承認された URL として使用して事前構成が行われた BaseAddressAuthorizationMessageHandler が用意されています。 アクセス トークンは、要求 URI がアプリのベース URI 内にある場合にのみ追加されます。 送信要求 URI がアプリのベース URI 内にない場合は、カスタム AuthorizationMessageHandler クラス (推奨) を使用するか、または AuthorizationMessageHandler を構成します。

注意

サーバー API アクセス用のクライアント アプリ構成に加えて、クライアントとサーバーが同じベース アドレス内に存在しない場合にクロスオリジン要求 (CORS) がサーバー API によって許可される必要があります。 サーバー側の CORS 構成の詳細については、この記事で後述する「クロスオリジン リソース共有 (CORS)」セクションを参照してください。

次の例では

using System.Net.Http;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;

...

builder.Services.AddHttpClient("WebAPI", 
        client => client.BaseAddress = new Uri("https://www.example.com/base"))
    .AddHttpMessageHandler<BaseAddressAuthorizationMessageHandler>();

builder.Services.AddScoped(sp => sp.GetRequiredService<IHttpClientFactory>()
    .CreateClient("WebAPI"));

Blazor WebAssembly プロジェクト テンプレートに基づいてホストされている Blazor ソリューションの場合、既定では、要求 URI はアプリのベース URI 内にあります。 したがって、IWebAssemblyHostEnvironment.BaseAddress (new Uri(builder.HostEnvironment.BaseAddress)) は、プロジェクト テンプレートから生成されたアプリ内の HttpClient.BaseAddress に割り当てられます。

構成された HttpClient を使用し、try-catch パターンを使用して、承認された要求を行います。

@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
@inject HttpClient Http

...

protected override async Task OnInitializedAsync()
{
    private ExampleType[] examples;

    try
    {
        examples = 
            await Http.GetFromJsonAsync<ExampleType[]>("ExampleAPIMethod");

        ...
    }
    catch (AccessTokenNotAvailableException exception)
    {
        exception.Redirect();
    }
}

カスタム AuthorizationMessageHandler クラス

このセクションのこのガイダンスは、アプリのベース URI 内に存在しない URI に対して送信要求を行うクライアント アプリに推奨されます。

次の例では、カスタム クラスによって、AuthorizationMessageHandler が、HttpClient 用の DelegatingHandler として使用するために拡張されます。 ConfigureHandler によって、アクセス トークンを使用して送信 HTTP 要求を承認できるように、このハンドラーが構成されます。 アクセス トークンがアタッチされるのは、承認された URL の少なくとも 1 つが要求 URI (HttpRequestMessage.RequestUri) のベースである場合に限られます。

using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;

public class CustomAuthorizationMessageHandler : AuthorizationMessageHandler
{
    public CustomAuthorizationMessageHandler(IAccessTokenProvider provider, 
        NavigationManager navigationManager)
        : base(provider, navigationManager)
    {
        ConfigureHandler(
            authorizedUrls: new[] { "https://www.example.com/base" },
            scopes: new[] { "example.read", "example.write" });
    }
}

Program.cs では、CustomAuthorizationMessageHandler がスコープされたサービスとして登録され、名前付き HttpClient によって作成された送信 HttpResponseMessage インスタンスの DelegatingHandler として構成されます。

builder.Services.AddScoped<CustomAuthorizationMessageHandler>();

builder.Services.AddHttpClient("WebAPI",
        client => client.BaseAddress = new Uri("https://www.example.com/base"))
    .AddHttpMessageHandler<CustomAuthorizationMessageHandler>();

Blazor WebAssembly プロジェクト テンプレートに基づいてホストされている Blazor ソリューションの場合、既定では、IWebAssemblyHostEnvironment.BaseAddress (new Uri(builder.HostEnvironment.BaseAddress)) が HttpClient.BaseAddress に割り当てられます。

構成が行われた HttpClient を使用し、try-catch パターンを使用して、承認された要求を行います。 CreateClient (Microsoft.Extensions.Http パッケージ) を使用してクライアントが作成される場合にサーバー API への要求を行うと、アクセス トークンが含まれるインスタンスが HttpClient に提供されます。 要求 URI が次の例 (ExampleAPIMethod) のように相対 URI である場合、それは、クライアント アプリから要求があったときに BaseAddress に結合されます。

@inject IHttpClientFactory ClientFactory

...

@code {
    private ExampleType[] examples;

    protected override async Task OnInitializedAsync()
    {
        try
        {
            var client = ClientFactory.CreateClient("WebAPI");

            examples = 
                await client.GetFromJsonAsync<ExampleType[]>("ExampleAPIMethod");
        }
        catch (AccessTokenNotAvailableException exception)
        {
            exception.Redirect();
        }
    }
}

AuthorizationMessageHandler を構成する

ConfigureHandler メソッドを使用すれば、承認された URL、スコープ、および戻り先 URL で AuthorizationMessageHandler を構成できます。 ConfigureHandler によって、アクセス トークンを使用して送信 HTTP 要求を承認できるように、ハンドラーが構成されます。 アクセス トークンがアタッチされるのは、承認された URL の少なくとも 1 つが要求 URI (HttpRequestMessage.RequestUri) のベースである場合に限られます。 要求 URI が相対 URI である場合、これは BaseAddress に結合されます。

次の例では、Program.cs で、AuthorizationMessageHandler によって HttpClient が構成されます。

using System.Net.Http;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;

...

builder.Services.AddScoped(sp => new HttpClient(
    sp.GetRequiredService<AuthorizationMessageHandler>()
    .ConfigureHandler(
        authorizedUrls: new[] { "https://www.example.com/base" },
        scopes: new[] { "example.read", "example.write" }))
    {
        BaseAddress = new Uri("https://www.example.com/base")
    });

Blazor WebAssembly プロジェクト テンプレートに基づいてホストされている Blazor ソリューションの場合、既定では、IWebAssemblyHostEnvironment.BaseAddress が以下に割り当てられます。

型指定された HttpClient

単一クラス内のすべての HTTP およびトークンの取得に関する問題を処理する、型指定されたクライアントを定義できます。

WeatherForecastClient.cs:

using System.Net.Http;
using System.Net.Http.Json;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
using static {APP ASSEMBLY}.Data;

public class WeatherForecastClient
{
    private readonly HttpClient http;

    public WeatherForecastClient(HttpClient http)
    {
        this.http = http;
    }

    public async Task<WeatherForecast[]> GetForecastAsync()
    {
        var forecasts = new WeatherForecast[0];

        try
        {
            forecasts = await http.GetFromJsonAsync<WeatherForecast[]>(
                "WeatherForecast");
        }
        catch (AccessTokenNotAvailableException exception)
        {
            exception.Redirect();
        }

        return forecasts;
    }
}

プレースホルダー {APP ASSEMBLY} は、アプリのアセンブリ名です (例: using static BlazorSample.Data;)。

Program.csの場合:

using System.Net.Http;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;

...

builder.Services.AddHttpClient<WeatherForecastClient>(
        client => client.BaseAddress = new Uri("https://www.example.com/base"))
    .AddHttpMessageHandler<BaseAddressAuthorizationMessageHandler>();

Blazor WebAssembly プロジェクト テンプレートに基づいてホストされている Blazor ソリューションの場合、既定では、IWebAssemblyHostEnvironment.BaseAddress (new Uri(builder.HostEnvironment.BaseAddress)) が HttpClient.BaseAddress に割り当てられます。

FetchData コンポーネント (Pages/FetchData.razor):

@inject WeatherForecastClient Client

...

protected override async Task OnInitializedAsync()
{
    forecasts = await Client.GetForecastAsync();
}

HttpClient ハンドラーを構成する

ハンドラーは、送信 HTTP 要求の ConfigureHandler を使用して、さらに構成を行うことができます。

Program.csの場合:

builder.Services.AddHttpClient<WeatherForecastClient>(
        client => client.BaseAddress = new Uri("https://www.example.com/base"))
    .AddHttpMessageHandler(sp => sp.GetRequiredService<AuthorizationMessageHandler>()
    .ConfigureHandler(
        authorizedUrls: new [] { "https://www.example.com/base" },
        scopes: new[] { "example.read", "example.write" }));

Blazor WebAssembly プロジェクト テンプレートに基づいてホストされている Blazor ソリューションの場合、既定では、IWebAssemblyHostEnvironment.BaseAddress が以下に割り当てられます。

セキュリティで保護された既定のクライアントを使用する、アプリ内の認証または承認されていない Web API 要求

通常、Blazor WebAssembly アプリがセキュリティで保護された既定の HttpClient を使用する場合、アプリでは、名前付きの HttpClient の構成を行うことで、認証または承認されていない Web API 要求が行われます。

Program.csの場合:

builder.Services.AddHttpClient("WebAPI.NoAuthenticationClient", 
    client => client.BaseAddress = new Uri("https://www.example.com/base"));

Blazor WebAssembly プロジェクト テンプレートに基づいてホストされている Blazor ソリューションの場合、既定では、IWebAssemblyHostEnvironment.BaseAddress (new Uri(builder.HostEnvironment.BaseAddress)) が HttpClient.BaseAddress に割り当てられます。

前述の登録は、セキュリティで保護された既定の HttpClient 登録に追加されます。

コンポーネントでは、IHttpClientFactory (Microsoft.Extensions.Http パッケージ) から HttpClient が作成され、認証または承認されていない要求が行われます。

@inject IHttpClientFactory ClientFactory

...

@code {
    private WeatherForecast[] forecasts;

    protected override async Task OnInitializedAsync()
    {
        var client = ClientFactory.CreateClient("WebAPI.NoAuthenticationClient");

        forecasts = await client.GetFromJsonAsync<WeatherForecast[]>(
            "WeatherForecastNoAuthentication");
    }
}

注意

サーバー API のコントローラー (前の例では WeatherForecastNoAuthenticationController) では、[Authorize] 属性を使用してマークされることはありません。

既定の HttpClient インスタンスとしてセキュリティで保護されたクライアントを使用するか、セキュリティで保護されていないクライアントを使用するかは、開発者が決定します。 この決定を行う方法の 1 つは、アプリが通信する認証済みのエンドポイントと認証されていないエンドポイントの数を考慮することです。 アプリの要求の大部分が API エンドポイントをセキュリティで保護する場合は、認証された HttpClient インスタンスを既定として使用します。 それ以外の場合は、認証されていない HttpClient インスタンスを既定として登録します。

IHttpClientFactory を使用する別の方法として、匿名エンドポイントへの認証されていないアクセス用に、型指定されたクライアントを作成することもできます。

追加のアクセス トークンを要求する

アクセス トークンは、IAccessTokenProvider.RequestAccessToken を呼び出して手動で取得できます。 次の例では、既定の HttpClient に対して、アプリが追加のスコープを必要としています。 Microsoft Authentication Library (MSAL) の例では、MsalProviderOptions を使用してスコープが構成されます。

Program.csの場合:

builder.Services.AddMsalAuthentication(options =>
{
    ...

    options.ProviderOptions.AdditionalScopesToConsent.Add("{CUSTOM SCOPE 1}");
    options.ProviderOptions.AdditionalScopesToConsent.Add("{CUSTOM SCOPE 2}");
}

前の例の {CUSTOM SCOPE 1}{CUSTOM SCOPE 2} のプレースホルダーは、カスタム スコープです。

IAccessTokenProvider.RequestToken メソッドには、指定されたスコープ セットを使用して、アプリでアクセス トークンをプロビジョニングできるようにするオーバーロードが用意されています。

Razor コンポーネントでは、次のようになります。

@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
@inject IAccessTokenProvider TokenProvider

...

var tokenResult = await TokenProvider.RequestAccessToken(
    new AccessTokenRequestOptions
    {
        Scopes = new[] { "{CUSTOM SCOPE 1}", "{CUSTOM SCOPE 2}" }
    });

if (tokenResult.TryGetToken(out var token))
{
    ...
}

前の例の {CUSTOM SCOPE 1}{CUSTOM SCOPE 2} のプレースホルダーは、カスタム スコープです。

AccessTokenResult.TryGetToken が次のように返します。

  • token を使用する場合は true
  • トークンが取得されない場合は false

クロスオリジン リソース共有 (CORS)

CORS 要求で資格情報 (承認 cookie またはヘッダー) を送信するときには、CORS ポリシーで Authorization ヘッダーが許可されている必要があります。

次のポリシーには、以下についての構成が含まれています。

  • 要求元 (http://localhost:5000https://localhost:5001)。
  • 任意のメソッド (動詞)。
  • Content-Type ヘッダーと Authorization ヘッダー。 カスタム ヘッダー (x-custom-headerなど) を許可するには、WithHeaders の呼び出し時にヘッダーを一覧表示します。
  • クライアント側の JavaScript コードによって設定された資格情報 (credentials プロパティが includeに設定されています)。
app.UseCors(policy => 
    policy.WithOrigins("http://localhost:5000", "https://localhost:5001")
    .AllowAnyMethod()
    .WithHeaders(HeaderNames.ContentType, HeaderNames.Authorization, "x-custom-header")
    .AllowCredentials());

Blazor WebAssembly プロジェクト テンプレートに基づいてホストされている Blazor ソリューションを使用すると、クライアント アプリとサーバー アプリに同じベース アドレスが使用されます。 クライアント アプリの HttpClient.BaseAddress は、既定では builder.HostEnvironment.BaseAddress の URI に設定されます。 ホストされている Blazor ソリューションの既定の構成では、CORS 構成は必須では ありません。 サーバー プロジェクトでホストされておらず、サーバー アプリのベース アドレスを共有していない追加のクライアント アプリでは、サーバー プロジェクト内の CORS 構成は 必須です

詳細については、「ASP.NET Core でクロスオリジン要求 (CORS) を有効にする」と、サンプル アプリの HTTP 要求テスター コンポーネント (Components/HTTPRequestTester.razor) を参照してください。

トークン要求エラーを処理する

シングル ページ アプリケーション (SPA) で OpenID Connect (OIDC) を使用してユーザーが認証されると、ユーザーが資格情報を入力したときに設定されるセッション cookie の形式で、SPA 内および Identity プロバイダー (IP) 内で、認証状態がローカルに維持されます。

通常、IP によってユーザーに出力されるトークンが有効なのは短時間のため (通常は約 1 時間)、クライアント アプリでは定期的に新しいトークンをフェッチする必要があります。 それ以外の場合は、許可されたトークンの有効期限が切れると、ユーザーがログアウトします。 ほとんどの場合、OIDC クライアントでは、ユーザーに対して認証の再要求を行うことなく、新しいトークンをプロビジョニングできます。これは、認証状態または IP 内に保持される "セッション" によるものです。

場合によっては、ユーザーの介入なしに、クライアントでトークンを取得できないことがあります。たとえば、何らかの理由でユーザーが明示的に IP からログアウトした場合などです。 このシナリオは、ユーザーが https://login.microsoftonline.com にアクセスしてログアウトした場合に発生します。これらのシナリオでは、ユーザーがログアウトしたことを、アプリはすぐに認識しません。クライアントで保持されるトークンは、有効でなくなった可能性があります。 また、クライアントでは、現在のトークンの有効期限が切れた後に、ユーザーの介入なしに新しいトークンをプロビジョニングすることはできません。

これらのシナリオは、トークンベースの認証に固有のものではありません。 これらは、SPA の性質の一部です。 また、認証 cookie が削除されると、cookie を使用する SPA でサーバー API を呼び出すこともできません。

保護されたリソースに対する API 呼び出しをアプリで実行するときは、次の点に注意する必要があります。

  • API を呼び出すための新しいアクセス トークンをプロビジョニングするには、ユーザーの再認証が必要です。
  • 有効かもしれないトークンがクライアントにある場合でも、そのトークンはユーザーによって取り消されているため、サーバーへの呼び出しが失敗する可能性があります。

アプリでトークンが要求されている場合は、次の 2 つの結果が考えられます。

  • 要求が成功します。アプリには有効なトークンがあります。
  • 要求が失敗します。新しいトークンを取得するために、ユーザーの再認証が必要となります。

トークン要求が失敗した場合は、リダイレクトを実行する前に、現在の状態を保存するかどうかを決定する必要があります。 次のようないくつかの方法がありますが、さらに複雑になります。

  • 現在のページの状態をセッション ストレージに格納します。 OnInitializedAsync ライフサイクル メソッド (OnInitializedAsync) 中に、続行する前に状態を復元できるかどうかを確認します。
  • クエリ文字列パラメーターを追加して、以前に保存した状態を再ハイドレートする必要があることをアプリに通知する方法として使用します。
  • 他の項目と競合するリスクなしにセッション ストレージにデータを格納するための一意識別子を持つクエリ文字列パラメーターを追加します。

以下の例では、次のことを行っています。

  • ログイン ページにリダイレクトする前に、状態を保持します。
  • クエリ文字列パラメーターを使用して、認証後に以前の状態を回復します。
...
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
@inject IAccessTokenProvider TokenProvider
...

<EditForm Model="User" @onsubmit="OnSaveAsync">
    <label>User
        <InputText @bind-Value="User.Name" />
    </label>
    <label>Last name
        <InputText @bind-Value="User.LastName" />
    </label>
</EditForm>

@code {
    public class Profile
    {
        public string Name { get; set; }
        public string LastName { get; set; }
    }

    public Profile User { get; set; } = new Profile();

    protected override async Task OnInitializedAsync()
    {
        var currentQuery = new Uri(Navigation.Uri).Query;

        if (currentQuery.Contains("state=resumeSavingProfile"))
        {
            User = await JS.InvokeAsync<Profile>("sessionStorage.getState", 
                "resumeSavingProfile");
        }
    }

    public async Task OnSaveAsync()
    {
        var http = new HttpClient();
        http.BaseAddress = new Uri(Navigation.BaseUri);

        var resumeUri = Navigation.Uri + $"?state=resumeSavingProfile";

        var tokenResult = await TokenProvider.RequestAccessToken(
            new AccessTokenRequestOptions
            {
                ReturnUrl = resumeUri
            });

        if (tokenResult.TryGetToken(out var token))
        {
            http.DefaultRequestHeaders.Add("Authorization", 
                $"Bearer {token.Value}");
            await http.PostAsJsonAsync("Save", User);
        }
        else
        {
            await JS.InvokeVoidAsync("sessionStorage.setState", 
                "resumeSavingProfile", User);
            Navigation.NavigateTo(tokenResult.RedirectUrl);
        }
    }
}

認証操作の前にアプリの状態を保存する

認証操作中に、ブラウザーが IP にリダイレクトされる前に、アプリの状態を保存することが必要になる場合があります。 状態コンテナーを使用していて、認証が成功した後に状態を復元する場合には、このようなことが起こる可能性があります。 カスタム認証状態オブジェクトを使用して、アプリ固有の状態、またはその参照を保持し、認証操作が正常に完了した後で、その状態を復元することができます。 このアプローチの例を次に示します。

状態コンテナー クラスは、アプリの状態値を保持するプロパティを使用して、アプリ内に作成されます。 次の例では、コンテナーを使用して、既定の Blazor プロジェクト テンプレートCounter コンポーネント (Pages/Counter.razor) のカウンター値を維持します。 コンテナーをシリアル化および逆シリアル化するためのメソッドは、System.Text.Json に基づいています。

using System.Text.Json;

public class StateContainer
{
    public int CounterValue { get; set; }

    public string GetStateForLocalStorage()
    {
        return JsonSerializer.Serialize(this);
    }

    public void SetStateFromLocalStorage(string locallyStoredState)
    {
        var deserializedState = 
            JsonSerializer.Deserialize<StateContainer>(locallyStoredState);

        CounterValue = deserializedState.CounterValue;
    }
}

Counter コンポーネントでは、状態コンテナーを使用して、コンポーネントの外部に currentCount 値を維持します。

@page "/counter"
@inject StateContainer State

<h1>Counter</h1>

<p>Current count: @currentCount</p>

<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>

@code {
    private int currentCount = 0;

    protected override void OnInitialized()
    {
        if (State.CounterValue > 0)
        {
            currentCount = State.CounterValue;
        }
    }

    private void IncrementCount()
    {
        currentCount++;
        State.CounterValue = currentCount;
    }
}

RemoteAuthenticationState から ApplicationAuthenticationState を作成します。 ローカルに格納されている状態の識別子として機能する Id プロパティを指定します。

ApplicationAuthenticationState.cs:

using Microsoft.AspNetCore.Components.WebAssembly.Authentication;

public class ApplicationAuthenticationState : RemoteAuthenticationState
{
    public string Id { get; set; }
}

Authentication コンポーネント (Pages/Authentication.razor) では、StateContainer のシリアル化と逆シリアル化の方法である GetStateForLocalStorage および SetStateFromLocalStorage で、ローカル セッション ストレージを使用してアプリの状態を保存および復元します。

@page "/authentication/{action}"
@inject IJSRuntime JS
@inject StateContainer State
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication

<RemoteAuthenticatorViewCore Action="@Action"
                             TAuthenticationState="ApplicationAuthenticationState"
                             AuthenticationState="AuthenticationState"
                             OnLogInSucceeded="RestoreState"
                             OnLogOutSucceeded="RestoreState" />

@code {
    [Parameter]
    public string Action { get; set; }

    public ApplicationAuthenticationState AuthenticationState { get; set; } =
        new ApplicationAuthenticationState();

    protected override async Task OnInitializedAsync()
    {
        if (RemoteAuthenticationActions.IsAction(RemoteAuthenticationActions.LogIn,
            Action) ||
            RemoteAuthenticationActions.IsAction(RemoteAuthenticationActions.LogOut,
            Action))
        {
            AuthenticationState.Id = Guid.NewGuid().ToString();

            await JS.InvokeVoidAsync("sessionStorage.setItem",
                AuthenticationState.Id, State.GetStateForLocalStorage());
        }
    }

    private async Task RestoreState(ApplicationAuthenticationState state)
    {
        if (state.Id != null)
        {
            var locallyStoredState = await JS.InvokeAsync<string>(
                "sessionStorage.getItem", state.Id);

            if (locallyStoredState != null)
            {
                State.SetStateFromLocalStorage(locallyStoredState);
                await JS.InvokeVoidAsync("sessionStorage.removeItem", state.Id);
            }
        }
    }
}

この例では、Azure Active Directory (AAD) を使用して認証を行います。 Program.csの場合:

  • ApplicationAuthenticationState は、Microsoft Authentication Library (MSAL) の RemoteAuthenticationState 型として、構成が行われます。
  • 状態コンテナーがサービス コンテナーに登録されます。
builder.Services.AddMsalAuthentication<ApplicationAuthenticationState>(options =>
{
    builder.Configuration.Bind("AzureAd", options.ProviderOptions.Authentication);
});

builder.Services.AddSingleton<StateContainer>();

アプリ ルートをカスタマイズする

Microsoft.AspNetCore.Components.WebAssembly.Authentication ライブラリでは既定で、次の表に示すルートを使用して、さまざまな認証状態が表されます。

ルート 目的
authentication/login サインイン操作をトリガーします。
authentication/login-callback サインイン操作の結果を処理します。
authentication/login-failed 何らかの理由でサインイン操作が失敗した場合に、エラー メッセージを表示します。
authentication/logout サインアウト操作をトリガーします。
authentication/logout-callback サインアウト操作の結果を処理します。
authentication/logout-failed 何らかの理由でサインアウト操作が失敗した場合に、エラー メッセージを表示します。
authentication/logged-out ユーザーが正常にログアウトしたことを示します。
authentication/profile ユーザー プロファイルを編集する操作をトリガーします。
authentication/register 新しいユーザーを登録する操作をトリガーします。

上の表に示すルートは、RemoteAuthenticationOptions<TRemoteAuthenticationProviderOptions>.AuthenticationPaths を使用して構成できます。 カスタム ルートを提供するオプションを設定する場合は、アプリに各パスを処理するルートがあることを確認します。

次の例では、すべてのパスが /security で始まります。

Authentication コンポーネント (Pages/Authentication.razor):

@page "/security/{action}"
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication

<RemoteAuthenticatorView Action="@Action" />

@code{
    [Parameter]
    public string Action { get; set; }
}

Program.csの場合:

builder.Services.AddApiAuthorization(options => { 
    options.AuthenticationPaths.LogInPath = "security/login";
    options.AuthenticationPaths.LogInCallbackPath = "security/login-callback";
    options.AuthenticationPaths.LogInFailedPath = "security/login-failed";
    options.AuthenticationPaths.LogOutPath = "security/logout";
    options.AuthenticationPaths.LogOutCallbackPath = "security/logout-callback";
    options.AuthenticationPaths.LogOutFailedPath = "security/logout-failed";
    options.AuthenticationPaths.LogOutSucceededPath = "security/logged-out";
    options.AuthenticationPaths.ProfilePath = "security/profile";
    options.AuthenticationPaths.RegisterPath = "security/register";
});

完全に異なるパスを必要とする場合は、前述のようにルートを設定し、明示的なアクション パラメーターを使用して RemoteAuthenticatorView をレンダリングします。

@page "/register"

<RemoteAuthenticatorView Action="@RemoteAuthenticationActions.Register" />

UI を別のページに分割することもできます。

認証ユーザー インターフェイスをカスタマイズする

RemoteAuthenticatorView には、各認証状態の UI 部分の既定のセットが含まれます。 各状態は、カスタム RenderFragment を渡すことでカスタマイズできます。 最初のログイン プロセス中に表示されるテキストをカスタマイズするには、次のように RemoteAuthenticatorView を変更します。

Authentication コンポーネント (Pages/Authentication.razor):

@page "/security/{action}"
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication

<RemoteAuthenticatorView Action="@Action">
    <LoggingIn>
        You are about to be redirected to https://login.microsoftonline.com.
    </LoggingIn>
</RemoteAuthenticatorView>

@code{
    [Parameter]
    public string Action { get; set; }
}

RemoteAuthenticatorView には、次の表に示す認証ルートごとに使用できるフラグメントが 1 つあります。

ルート Fragment
authentication/login <LoggingIn>
authentication/login-callback <CompletingLoggingIn>
authentication/login-failed <LogInFailed>
authentication/logout <LogOut>
authentication/logout-callback <CompletingLogOut>
authentication/logout-failed <LogOutFailed>
authentication/logged-out <LogOutSucceeded>
authentication/profile <UserProfile>
authentication/register <Registering>

ユーザーをカスタマイズする

アプリにバインドされているユーザーをカスタマイズできます。

ペイロード要求を使用してユーザーをカスタマイズする

次の例では、アプリの認証されたユーザーが、ユーザーの認証方法ごとに amr 要求を受け取ります。 この amr 要求は、Microsoft Identity プラットフォーム v1.0 ペイロード要求でトークンのサブジェクトが認証された方法を示しています。 この例では、RemoteUserAccount に基づくカスタム ユーザー アカウント クラスを使用します。

RemoteUserAccount クラスを拡張するクラスを作成します。 次の例では、AuthenticationMethod プロパティを amr JSON プロパティ値のユーザー配列に設定します。 ユーザーが認証されると、フレームワークによって AuthenticationMethod が自動的に設定されます。

using System.Text.Json.Serialization;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;

public class CustomUserAccount : RemoteUserAccount
{
    [JsonPropertyName("amr")]
    public string[] AuthenticationMethod { get; set; }
}

AccountClaimsPrincipalFactory<TAccount> を拡張するファクトリを作成して、CustomUserAccount.AuthenticationMethod に格納されているユーザーの認証方法から要求を作成します。

using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication.Internal;

public class CustomAccountFactory 
    : AccountClaimsPrincipalFactory<CustomUserAccount>
{
    public CustomAccountFactory(NavigationManager navigationManager, 
        IAccessTokenProviderAccessor accessor) : base(accessor)
    {
    }

    public override async ValueTask<ClaimsPrincipal> CreateUserAsync(
        CustomUserAccount account, RemoteAuthenticationUserOptions options)
    {
        var initialUser = await base.CreateUserAsync(account, options);

        if (initialUser.Identity.IsAuthenticated)
        {
            foreach (var value in account.AuthenticationMethod)
            {
                ((ClaimsIdentity)initialUser.Identity)
                    .AddClaim(new Claim("amr", value));
            }
        }

        return initialUser;
    }
}

使用中の認証プロバイダーの CustomAccountFactory を登録します。 次の登録のいずれかが有効です。

  • AddOidcAuthentication:

    using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
    
    ...
    
    builder.Services.AddOidcAuthentication<RemoteAuthenticationState, 
        CustomUserAccount>(options =>
        {
            ...
        })
        .AddAccountClaimsPrincipalFactory<RemoteAuthenticationState, 
            CustomUserAccount, CustomAccountFactory>();
    
  • AddMsalAuthentication:

    using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
    
    ...
    
    builder.Services.AddMsalAuthentication<RemoteAuthenticationState, 
        CustomUserAccount>(options =>
        {
            ...
        })
        .AddAccountClaimsPrincipalFactory<RemoteAuthenticationState, 
            CustomUserAccount, CustomAccountFactory>();
    
  • AddApiAuthorization:

    using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
    
    ...
    
    builder.Services.AddApiAuthorization<RemoteAuthenticationState, 
        CustomUserAccount>(options =>
        {
            ...
        })
        .AddAccountClaimsPrincipalFactory<RemoteAuthenticationState, 
            CustomUserAccount, CustomAccountFactory>();
    

カスタム ユーザー アカウント クラスを持つ AAD セキュリティ グループおよびロール

AAD セキュリティ グループと AAD 管理者ロール、およびカスタム ユーザー アカウント クラスを使用するその他の例については、Azure Active Directory のグループとロールを使用する ASP.NET Core Blazor WebAssembly を参照してください。

認証を使用したプリレンダリングのサポート

現在、認証と認可を必要とするプリレンダリング コンテンツはサポートされていません。 Blazor WebAssembly セキュリティ アプリのトピックのいずれかのガイダンスを実行した後は、この後の手順に従って次のようなアプリを作成できます。

  • 承認が不要なパスをプリレンダリングする。
  • 承認が必要なパスをプリレンダリングしない。

クライアント ( Client ) アプリの Program クラス (Program.cs) で、共通のサービスの登録を別のメソッド (たとえば、ConfigureCommonServices) に組み入れます。 一般的なサービスは、開発者がクライアントとサーバー ( Server ) アプリの両方で使用するために登録するものです。

public class Program
{
    public static async Task Main(string[] args)
    {
        var builder = WebAssemblyHostBuilder.CreateDefault(args);
        builder.RootComponents.Add...;

        // Services that only the client app uses
        builder.Services.AddScoped( ... );

        ConfigureCommonServices(builder.Services);

        await builder.Build().RunAsync();
    }

    public static void ConfigureCommonServices(IServiceCollection services)
    {
        // Common service registrations that both apps use
        services.Add...;
    }
}

サーバー アプリの Startup.ConfigureServices で、次の追加サービスを登録し、ConfigureCommonServices を呼び出します。

using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.AspNetCore.Components.Server;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;

public void ConfigureServices(IServiceCollection services)
{
    ...

    services.AddRazorPages();
    services.AddScoped<AuthenticationStateProvider, 
        ServerAuthenticationStateProvider>();
    services.AddScoped<SignOutSessionStateManager>();

    Client.Program.ConfigureCommonServices(services);
}

サーバー アプリの Startup.Configure メソッドで、endpoints.MapFallbackToFile("index.html")endpoints.MapFallbackToPage("/_Host") に置き換えます。

app.UseEndpoints(endpoints =>
{
    endpoints.MapControllers();
    endpoints.MapFallbackToPage("/_Host");
});

サーバー アプリで、Pages フォルダーが存在しない場合は作成します。 サーバー アプリの Pages フォルダー内に _Host.cshtml ページを作成します。 クライアント アプリの wwwroot/index.html ファイルの内容を Pages/_Host.cshtml ファイルに貼り付けます。 ファイルの内容を更新します。

  • ファイルの先頭に、@page "_Host" を追加します。

  • <app>Loading...</app> タグを次のように置き換えます。

    <app>
        @if (!HttpContext.Request.Path.StartsWithSegments("/authentication"))
        {
            <component type="typeof({CLIENT APP ASSEMBLY NAME}.App)" 
                render-mode="Static" />
        }
        else
        {
            <text>Loading...</text>
        }
    </app>
    

    前の例では、プレースホルダー {CLIENT APP ASSEMBLY NAME} はクライアント アプリのアセンブリ名です (たとえば BlazorSample.Client)。

ホストされているアプリおよびサードパーティ ログイン プロバイダーに関するオプション

ホストされている Blazor WebAssembly アプリをサードパーティ プロバイダーで認証および承認する場合、ユーザーの認証にはいくつかのオプションを使用できます。 どれを選択するかは、シナリオによって異なります。

詳細については、「ASP.NET Core で外部プロバイダーからの追加の要求とトークンを保持する」を参照してください。

ユーザーを認証して保護されたサードパーティ API のみを呼び出す

サードパーティ API プロバイダーに対してクライアント側の OAuth フローを使用してユーザーを認証します。

builder.services.AddOidcAuthentication(options => { ... });

このシナリオでは:

  • アプリをホストしているサーバーは関与しません。
  • サーバー上の API を保護することはできません。
  • アプリでは、保護されたサードパーティ API のみを呼び出すことができます。

サードパーティ プロバイダーでユーザーを認証し、ホスト サーバーおよびサード パーティ上で保護された API を呼び出す

サードパーティのログイン プロバイダーを使用して、Identity の構成を行います。 サードパーティ API へのアクセスに必要なトークンを取得し、それを格納します。

ユーザーがログインすると、認証プロセスの一環として、アクセス トークンと更新トークンが IdentityID によって収集されます。 その時点で、サードパーティ API の API 呼び出しを行うために使用できる方法はいくつかあります。

サーバー アクセス トークンを使用してサードパーティのアクセス トークンを取得する

サーバー上で生成されたアクセス トークンを使用して、サーバー API エンドポイントからサードパーティのアクセストークンを取得します。 そこから、サードパーティのアクセス トークンを使用して、クライアント上の IdentityID からサードパーティ API リソースを直接呼び出します。

この方法はお勧めしません。 この方法では、サードパーティのアクセス トークンをパブリック クライアント用に生成されたものとして扱う必要があります。 OAuth 規約では、パブリック アプリにはクライアント シークレットがありません。これはシークレットを安全に格納することが信頼できないためです。アクセス トークンは機密クライアントに対して生成されます。 機密クライアントとは、クライアント シークレットを持っていてシークレットを安全に格納できると想定されるクライアントです。

  • サードパーティのアクセス トークンには、サードパーティがより信頼できるクライアントのトークンを生成したという事実に基づいて機密性の高い操作を実行するための追加のスコープが付与される場合があります。
  • 同様に、信頼されていないクライアントに更新トークンを発行してはなりません。それを行ってしまうと、他の制限が適用されない限り、クライアントは無制限にアクセスできます。

サードパーティ API を呼び出すために、クライアントからサーバー API への API 呼び出しを行う

クライアントからサーバー API への API 呼び出しを行います。 サーバーから、サードパーティ API リソースのアクセス トークンを取得し、必要な呼び出しはすべて発行します。

この方法では、サードパーティ API を呼び出すためにサーバー経由で追加のネットワーク ホップが必要になりますが、それによって最終的にはより安全なエクスペリエンスが得られます。

  • サーバーでは、更新トークンを格納し、アプリからサードパーティ リソースへのアクセスが決して失われないようにすることができます。
  • アプリでは、より機密性の高いアクセス許可を含む可能性のあるサーバーからのアクセス トークンをリークさせることはできません。

OpenID Connect (OIDC) v2.0 エンドポイントを使用する

認証ライブラリと Blazor プロジェクト テンプレートにより、OpenID Connect (OIDC) v1.0 エンドポイントが使用されます。 v2.0 エンドポイントを使用するには、JWT ベアラー JwtBearerOptions.Authority オプションの構成を行います。 次の例では、Authority プロパティに v2.0 セグメントを追加することで、v2.0 に対して AAD の構成が行われます。

builder.Services.Configure<JwtBearerOptions>(
    AzureADDefaults.JwtBearerAuthenticationScheme, 
    options =>
    {
        options.Authority += "/v2.0";
    });

または、アプリ設定ファイル (appsettings.json) で設定を行うこともできます。

{
  "Local": {
    "Authority": "https://login.microsoftonline.com/common/oauth2/v2.0/",
    ...
  }
}

証明機関へのセグメントを追跡することがアプリの OIDC プロバイダー (AAD 以外のプロバイダーなど) にとって適切でない場合は、Authority プロパティを直接設定します。 JwtBearerOptions またはアプリ設定ファイル (appsettings.json) で Authority キーを使用してプロパティを設定します。

ID トークンの要求のリストは、v2.0 エンドポイントで変更されています。 詳細については、「Microsoft ID プラットフォーム (v2.0) に更新する理由」を参照してください。

コンポーネントで gRPC を構成し、使用する

ASP.NET Core gRPC フレームワークを使用するように Blazor WebAssembly アプリを構成するには:

  • サーバーで gRPC-Web を有効にします。 詳細については、「ブラウザー アプリでの gRPC の使用」を参照してください。
  • アプリのメッセージ ハンドラーに gRPC サービスを登録します。 次の例では、gRPC チュートリアル (Program.cs) から GreeterClient サービスを使用するように、アプリの認可メッセージ ハンドラーが構成されます。
using System.Net.Http;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
using Grpc.Net.Client;
using Grpc.Net.Client.Web;
using {APP ASSEMBLY}.Shared;

...

builder.Services.AddScoped(sp =>
{
    var baseAddressMessageHandler = 
        sp.GetRequiredService<BaseAddressAuthorizationMessageHandler>();
    baseAddressMessageHandler.InnerHandler = new HttpClientHandler();
    var grpcWebHandler = 
        new GrpcWebHandler(GrpcWebMode.GrpcWeb, baseAddressMessageHandler);
    var channel = GrpcChannel.ForAddress(builder.HostEnvironment.BaseAddress, 
        new GrpcChannelOptions { HttpHandler = grpcWebHandler });

    return new Greeter.GreeterClient(channel);
});

プレースホルダー {APP ASSEMBLY} は、アプリのアセンブリ名です (例: BlazorSample)。 ホストされている Blazor ソリューションの Shared プロジェクトに .proto ファイルを置きます。

クライアント アプリのコンポーネントでは、gRPC クライアント (Pages/Grpc.razor) を使用し、gRPC 呼び出しを行うことができます。

@page "/grpc"
@using Microsoft.AspNetCore.Authorization
@using {APP ASSEMBLY}.Shared
@attribute [Authorize]
@inject Greeter.GreeterClient GreeterClient

<h1>Invoke gRPC service</h1>

<p>
    <input @bind="name" placeholder="Type your name" />
    <button @onclick="GetGreeting" class="btn btn-primary">Call gRPC service</button>
</p>

Server response: <strong>@serverResponse</strong>

@code {
    private string name = "Bert";
    private string serverResponse;

    private async Task GetGreeting()
    {
        try
        {
            var request = new HelloRequest { Name = name };
            var reply = await GreeterClient.SayHelloAsync(request);
            serverResponse = reply.Message;
        }
        catch (Grpc.Core.RpcException ex)
            when (ex.Status.DebugException is 
                AccessTokenNotAvailableException tokenEx)
        {
            tokenEx.Redirect();
        }
    }
}

プレースホルダー {APP ASSEMBLY} は、アプリのアセンブリ名です (例: BlazorSample)。 Status.DebugException プロパティを使用するには、Grpc.Net.Client バージョン 2.30.0 以降を使用します。

詳細については、「ブラウザー アプリでの gRPC の使用」を参照してください。

Authentication.MSAL JavaScript ライブラリのカスタム バージョンをビルドする

アプリでカスタム バージョンの JavaScript 用 Microsoft Authentication Library (MSAL js) が必要な場合は、次の手順を実行します。

  1. システムに最新の開発者用 .NET SDK がインストールされていることを確認するか、次から最新の開発者用 SDK を入手してインストールします: .NET Core SDK: インストーラーとバイナリ。 このシナリオでは、内部 NuGet フィードの構成は必要ありません。
  2. ソースから ASP.NET Core をビルドする」のドキュメントに従って、開発用の dotnet/aspnetcore GitHub リポジトリを設定します。 dotnet/aspnetcore GitHub リポジトリをフォークしてクローンするか、ZIP アーカイブをダウンロードします。
  3. src/Components/WebAssembly/Authentication.Msal/src/Interop/package.json ファイルを開き、必要なバージョンの @azure/msal-browser を設定します。 リリース済みバージョンの一覧については、@azure/msal-browser の npm Web サイトにアクセスし、 [Versions](バージョン) タブを選択してください。
  4. コマンド シェルで yarn build コマンドを使用して、src/Components/WebAssembly/Authentication.Msal/src フォルダー内に Authentication.Msal プロジェクトをビルドします。
  5. アプリで圧縮された資産 (Brotli/Gzip) を使用する場合は、Interop/dist/Release/AuthenticationService.js ファイルを圧縮します。
  6. AuthenticationService.js ファイルと、生成した場合はファイルの圧縮バージョン (.br/.gz) を、Interop/dist/Release フォルダーから、アプリの発行済み資産にあるアプリの publish/wwwroot/_content/Microsoft.Authentication.WebAssembly.Msal フォルダーにコピーします。

その他の技術情報