ASP.NET Core Blazor Hybrid の認証と承認

注意

これは、この記事の最新バージョンではありません。 現在のリリースについては、この記事の .NET 8 バージョンを参照してください。

警告

このバージョンの ASP.NET Core はサポート対象から除外されました。 詳細については、「.NET および .NET Core サポート ポリシー」を参照してください。 現在のリリースについては、この記事の .NET 8 バージョンを参照してください。

重要

この情報はリリース前の製品に関する事項であり、正式版がリリースされるまでに大幅に変更される可能性があります。 Microsoft はここに示されている情報について、明示か黙示かを問わず、一切保証しません。

現在のリリースについては、この記事の .NET 8 バージョンを参照してください。

この記事では、Blazor Hybrid アプリでのセキュリティと ASP.NET Core Identity の構成と管理に関する ASP.NET Core のサポートについて説明します。

Blazor Hybrid アプリの認証は、ネイティブ プラットフォーム ライブラリによって処理されます。これは、ブラウザー サンドボックスでは提供できない拡張セキュリティ保証を提供するためです。 ネイティブ アプリの認証は、OS 固有のメカニズムを使うか、OpenID Connect (OIDC) のようなフェデレーション プロトコルを介して行われます。 アプリ用に選んだ identity プロバイダーのガイダンスに従い、さらにこの記事のガイダンスを使って identity を Blazor と統合します。

認証を統合するには、Razor コンポーネントとサービスについて以下の目標を達成する必要があります。

  • Microsoft.AspNetCore.Components.Authorization パッケージ内で抽象化を使います (AuthorizeView など)。
  • 認証コンテキストの変更に対応します。
  • 認可された API 呼び出しを実行するアクセス トークンなど、identity プロバイダーからアプリによってプロビジョニングされた資格情報にアクセスします。

.NET MAUI、WPF、または Windows フォーム アプリに認証を追加し、ユーザーが正常にログインおよびログアウトできるようになったら、認証を Blazor と統合して、認証済みユーザーが Razor コンポーネントおよびサービスで使用できるようにします。 次の手順に従います。

  • Microsoft.AspNetCore.Components.Authorization パッケージを参照します。

    Note

    .NET アプリへのパッケージの追加に関するガイダンスについては、「パッケージ利用のワークフロー」 (NuGet ドキュメント) の "パッケージのインストールと管理" に関する記事を参照してください。 NuGet.org で正しいパッケージ バージョンを確認します。

  • カスタムの AuthenticationStateProvider を実装します。これは、認証済みユーザーに関する情報にアクセスし、認証状態が変化したときに更新を受け取る目的で Razor コンポーネントが使う抽象化です。

  • 依存関係挿入コンテナーにカスタムの認証状態プロバイダーを登録します。

.NET MAUI アプリは Xamarin.Essentials: Web Authenticator を使います。WebAuthenticator クラスにより、アプリに登録された特定の URL へのコールバックをリッスンするブラウザーベースの認証フローをアプリから開始できます。

その他のガイダンスについては、次のリソースを参照してください。

Microsoft Entra (ME-ID) および AAD B2C と統合するために、Windows フォーム アプリは Microsoft identity プラットフォームを使います。 詳細については、「Microsoft Authentication Library (MSAL) の概要」を参照してください。

ユーザー変更の更新がないカスタムの AuthenticationStateProvider を作成する

アプリの起動直後にアプリがユーザーを認証し、認証済みユーザーがアプリの有効期間全体にわたって同じままである場合、ユーザー変更通知は不要であり、アプリからは認証済みユーザーに関する情報のみが提供されます。 このシナリオでは、アプリを開いたときにユーザーがログインし、ユーザーがログアウトすると、アプリのログイン画面が再び表示されます。次の ExternalAuthStateProvider は、この認証シナリオに対応するカスタムの AuthenticationStateProvider の実装例です。

注意

次のカスタムの AuthenticationStateProvider では、コード例を任意の Blazor Hybrid アプリに適用できるように、名前空間を宣言していません。 ただし、運用アプリでこの例を実装する際には、アプリの名前空間を指定することがベスト プラクティスです。

ExternalAuthStateProvider.cs:

using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components.Authorization;

public class ExternalAuthStateProvider : AuthenticationStateProvider
{
    private readonly Task<AuthenticationState> authenticationState;

    public ExternalAuthStateProvider(AuthenticatedUser user) => 
        authenticationState = Task.FromResult(new AuthenticationState(user.Principal));

    public override Task<AuthenticationState> GetAuthenticationStateAsync() =>
        authenticationState;
}

public class AuthenticatedUser
{
    public ClaimsPrincipal Principal { get; set; } = new();
}

以下の手順では、次の方法について説明します。

  • 必要な名前空間を追加します。
  • 認可サービスと Blazor 抽象化をサービス コレクションに追加します。
  • サービス コレクションを構築します。
  • 認証済みユーザーの要求プリンシパルを設定するために AuthenticatedUser サービスを解決します。 詳細については、identity プロバイダーのドキュメントをご覧ください。
  • 構築したホストを返します。

MauiProgram.csMauiProgram.CreateMauiApp メソッドに、Microsoft.AspNetCore.Components.AuthorizationSystem.Security.Claims の名前空間を追加します。

using Microsoft.AspNetCore.Components.Authorization;
using System.Security.Claims;

構築した Microsoft.Maui.Hosting.MauiAppを返す次のコード行を削除します。

- return builder.Build();

前のコード行を、次のコードで置き換えます。 ユーザーを認証するために OpenID/MSAL コードを追加します。 詳細については、identity プロバイダーのドキュメントをご覧ください。

builder.Services.AddAuthorizationCore();
builder.Services.TryAddScoped<AuthenticationStateProvider, ExternalAuthStateProvider>();
builder.Services.AddSingleton<AuthenticatedUser>();
var host = builder.Build();

var authenticatedUser = host.Services.GetRequiredService<AuthenticatedUser>();

/*
Provide OpenID/MSAL code to authenticate the user. See your identity provider's 
documentation for details.

The user is represented by a new ClaimsPrincipal based on a new ClaimsIdentity.
*/
var user = new ClaimsPrincipal(new ClaimsIdentity());

authenticatedUser.Principal = user;

return host;

以下の手順では、次の方法について説明します。

  • 必要な名前空間を追加します。
  • 認可サービスと Blazor 抽象化をサービス コレクションに追加します。
  • サービス コレクションを構築し、構築したサービス コレクションをアプリの ResourceDictionary にリソースとして追加します。
  • 認証済みユーザーの要求プリンシパルを設定するために AuthenticatedUser サービスを解決します。 詳細については、identity プロバイダーのドキュメントをご覧ください。
  • 構築したホストを返します。

MainWindow のコンストラクター (MainWindow.xaml.cs) に Microsoft.AspNetCore.Components.AuthorizationSystem.Security.Claims の名前空間を追加します。

using Microsoft.AspNetCore.Components.Authorization;
using System.Security.Claims;

構築したサービス コレクションをアプリの ResourceDictionary にリソースとして追加する次のコード行を削除します。

- Resources.Add("services", serviceCollection.BuildServiceProvider());

前のコード行を、次のコードで置き換えます。 ユーザーを認証するために OpenID/MSAL コードを追加します。 詳細については、identity プロバイダーのドキュメントをご覧ください。

serviceCollection.AddAuthorizationCore();
serviceCollection.TryAddScoped<AuthenticationStateProvider, ExternalAuthStateProvider>();
serviceCollection.AddSingleton<AuthenticatedUser>();
var services = serviceCollection.BuildServiceProvider();
Resources.Add("services", services);

var authenticatedUser = services.GetRequiredService<AuthenticatedUser>();

/*
Provide OpenID/MSAL code to authenticate the user. See your identity provider's 
documentation for details.

The user is represented by a new ClaimsPrincipal based on a new ClaimsIdentity.
*/
var user = new ClaimsPrincipal(new ClaimsIdentity());

authenticatedUser.Principal = user;

以下の手順では、次の方法について説明します。

  • 必要な名前空間を追加します。
  • 認可サービスと Blazor 抽象化をサービス コレクションに追加します。
  • サービス コレクションを構築し、構築したサービス コレクションをアプリのサービス プロバイダーに追加します。
  • 認証済みユーザーの要求プリンシパルを設定するために AuthenticatedUser サービスを解決します。 詳細については、identity プロバイダーのドキュメントをご覧ください。

Form1 のコンストラクター (Form1.cs) に Microsoft.AspNetCore.Components.AuthorizationSystem.Security.Claims の名前空間を追加します。

using Microsoft.AspNetCore.Components.Authorization;
using System.Security.Claims;

構築したサービス コレクションをアプリのサービス プロバイダーに設定する次のコード行を削除します。

- blazorWebView1.Services = services.BuildServiceProvider();

前のコード行を、次のコードで置き換えます。 ユーザーを認証するために OpenID/MSAL コードを追加します。 詳細については、identity プロバイダーのドキュメントをご覧ください。

services.AddAuthorizationCore();
services.TryAddScoped<AuthenticationStateProvider, ExternalAuthStateProvider>();
services.AddSingleton<AuthenticatedUser>();
var serviceCollection = services.BuildServiceProvider();
blazorWebView1.Services = serviceCollection;

var authenticatedUser = serviceCollection.GetRequiredService<AuthenticatedUser>();

/*
Provide OpenID/MSAL code to authenticate the user. See your identity provider's 
documentation for details.

The user is represented by a new ClaimsPrincipal based on a new ClaimsIdentity.
*/
var user = new ClaimsPrincipal(new ClaimsIdentity());

authenticatedUser.Principal = user;

ユーザー変更の更新があるカスタムの AuthenticationStateProvider を作成する

Blazor アプリの実行中にユーザーを更新するには、AuthenticationStateProvider の実装内で、次の方法のいずれかを使って NotifyAuthenticationStateChanged を呼び出します。

BlazorWebView の外部から認証の更新を通知する (オプション 1)

カスタムの AuthenticationStateProvider からは、グローバル サービスを使って認証の更新を通知することができます。 AuthenticationStateProvider がサブスクライブし、そのイベントから NotifyAuthenticationStateChanged を呼び出すことができるイベントをサービスで用意することをお勧めします。

注意

次のカスタムの AuthenticationStateProvider では、コード例を任意の Blazor Hybrid アプリに適用できるように、名前空間を宣言していません。 ただし、運用アプリでこの例を実装する際には、アプリの名前空間を指定することがベスト プラクティスです。

ExternalAuthStateProvider.cs:

using System;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components.Authorization;

public class ExternalAuthStateProvider : AuthenticationStateProvider
{
    private AuthenticationState currentUser;

    public ExternalAuthStateProvider(ExternalAuthService service)
    {
        currentUser = new AuthenticationState(service.CurrentUser);

        service.UserChanged += (newUser) =>
        {
            currentUser = new AuthenticationState(newUser);
            NotifyAuthenticationStateChanged(Task.FromResult(currentUser));
        };
    }

    public override Task<AuthenticationState> GetAuthenticationStateAsync() =>
        Task.FromResult(currentUser);
}

public class ExternalAuthService
{
    public event Action<ClaimsPrincipal>? UserChanged;
    private ClaimsPrincipal? currentUser;

    public ClaimsPrincipal CurrentUser
    {
        get { return currentUser ?? new(); }
        set
        {
            currentUser = value;

            if (UserChanged is not null)
            {
                UserChanged(currentUser);
            }
        }
    }
}

MauiProgram.csMauiProgram.CreateMauiApp メソッドで、Microsoft.AspNetCore.Components.Authorization の名前空間を追加します。

using Microsoft.AspNetCore.Components.Authorization;

認可サービスと Blazor 抽象化をサービス コレクションに追加します。

builder.Services.AddAuthorizationCore();
builder.Services.TryAddScoped<AuthenticationStateProvider, ExternalAuthStateProvider>();
builder.Services.AddSingleton<ExternalAuthService>();

MainWindow のコンストラクター (MainWindow.xaml.cs) に Microsoft.AspNetCore.Components.Authorization の名前空間を追加します。

using Microsoft.AspNetCore.Components.Authorization;

認可サービスと Blazor 抽象化をサービス コレクションに追加します。

serviceCollection.AddAuthorizationCore();
serviceCollection.TryAddScoped<AuthenticationStateProvider, ExternalAuthStateProvider>();
serviceCollection.AddSingleton<ExternalAuthService>();

Form1 のコンストラクター (Form1.cs) に Microsoft.AspNetCore.Components.Authorization の名前空間を追加します。

using Microsoft.AspNetCore.Components.Authorization;

認可サービスと Blazor 抽象化をサービス コレクションに追加します。

services.AddAuthorizationCore();
services.TryAddScoped<AuthenticationStateProvider, ExternalAuthStateProvider>();
services.AddSingleton<ExternalAuthService>();

アプリがユーザーを認証するたびに、ExternalAuthService サービスを解決します。

var authService = host.Services.GetRequiredService<ExternalAuthService>();

ユーザーを認証するには、カスタムの OpenID/MSAL コードを実行します。 詳細については、identity プロバイダーのドキュメントをご覧ください。 認証済みユーザー (次の例の authenticatedUser) は、新しい ClaimsIdentity に基づく新しい ClaimsPrincipal です。

現在のユーザーを認証済みユーザーに設定します。

authService.CurrentUser = authenticatedUser;

先ほどの方法の代替案として、サービス経由で設定するのではなく、System.Threading.Thread.CurrentPrincipal に対してユーザーのプリンシパルを設定することができます。その結果、依存関係挿入コンテナーの使用は回避されます。

public class CurrentThreadUserAuthenticationStateProvider : AuthenticationStateProvider
{
    public override Task<AuthenticationState> GetAuthenticationStateAsync() =>
        Task.FromResult(
            new AuthenticationState(Thread.CurrentPrincipal as ClaimsPrincipal ?? 
                new ClaimsPrincipal(new ClaimsIdentity())));
}

この代替方法を使うと、認可サービス (AddAuthorizationCore) と CurrentThreadUserAuthenticationStateProvider (.TryAddScoped<AuthenticationStateProvider, CurrentThreadUserAuthenticationStateProvider>()) のみがサービス コレクションに追加されます。

BlazorWebView 内で認証を処理する (オプション 2)

カスタムの AuthenticationStateProvider には、ログインとログアウトをトリガーし、ユーザーを更新する追加のメソッドを含めることができます。

注意

次のカスタムの AuthenticationStateProvider では、コード例を任意の Blazor Hybrid アプリに適用できるように、名前空間を宣言していません。 ただし、運用アプリでこの例を実装する際には、アプリの名前空間を指定することがベスト プラクティスです。

ExternalAuthStateProvider.cs:

using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components.Authorization;

public class ExternalAuthStateProvider : AuthenticationStateProvider
{
    private ClaimsPrincipal currentUser = new ClaimsPrincipal(new ClaimsIdentity());

    public override Task<AuthenticationState> GetAuthenticationStateAsync() =>
        Task.FromResult(new AuthenticationState(currentUser));

    public Task LogInAsync()
    {
        var loginTask = LogInAsyncCore();
        NotifyAuthenticationStateChanged(loginTask);

        return loginTask;

        async Task<AuthenticationState> LogInAsyncCore()
        {
            var user = await LoginWithExternalProviderAsync();
            currentUser = user;

            return new AuthenticationState(currentUser);
        }
    }

    private Task<ClaimsPrincipal> LoginWithExternalProviderAsync()
    {
        /*
            Provide OpenID/MSAL code to authenticate the user. See your identity 
            provider's documentation for details.

            Return a new ClaimsPrincipal based on a new ClaimsIdentity.
        */
        var authenticatedUser = new ClaimsPrincipal(new ClaimsIdentity());

        return Task.FromResult(authenticatedUser);
    }

    public void Logout()
    {
        currentUser = new ClaimsPrincipal(new ClaimsIdentity());
        NotifyAuthenticationStateChanged(
            Task.FromResult(new AuthenticationState(currentUser)));
    }
}

前の例の場合:

  • LogInAsyncCore を呼び出すと、ログイン プロセスがトリガーされます。
  • NotifyAuthenticationStateChanged を呼び出すと、更新が進行中であることが通知されます。これにより、アプリはログインまたはログアウト プロセス中に一時的な UI を提供できます。
  • loginTask を返すと、タスクが戻ります。そのため、ログインをトリガーしたコンポーネントは待機し、タスクの完了後に対応することができます。
  • LoginWithExternalProviderAsync メソッドは、開発者が identity プロバイダーの SDK を使ってユーザーをログインするために実装します。 詳細については、identity プロバイダのドキュメントをご覧ください。 認証済みユーザー (authenticatedUser) は、新しい ClaimsIdentity に基づいた新しい ClaimsPrincipal です。

MauiProgram.csMauiProgram.CreateMauiApp メソッドで、認可サービスと Blazor 抽象化をサービス コレクションに追加します。

builder.Services.AddAuthorizationCore();
builder.Services.TryAddScoped<AuthenticationStateProvider, ExternalAuthStateProvider>();

MainWindow のコンストラクター (MainWindow.xaml.cs) で、認可サービスと Blazor 抽象化をサービス コレクションに追加します。

serviceCollection.AddAuthorizationCore();
serviceCollection.TryAddScoped<AuthenticationStateProvider, ExternalAuthStateProvider>();

Form1 のコンストラクター (Form1.cs) で、認可サービスと Blazor 抽象化をサービス コレクションに追加します。

services.AddAuthorizationCore();
services.TryAddScoped<AuthenticationStateProvider, ExternalAuthStateProvider>();

次の LoginComponent コンポーネントは、ユーザーをログインする方法を示しています。 一般的なアプリでは、ユーザーがアプリにログインしていない場合にのみ、親コンポーネント内の LoginComponent コンポーネントが表示されます。

Shared/LoginComponent.razor:

@inject AuthenticationStateProvider AuthenticationStateProvider

<button @onclick="Login">Log in</button>

@code
{
    public async Task Login()
    {
        await ((ExternalAuthStateProvider)AuthenticationStateProvider)
            .LogInAsync();
    }
}

次の LogoutComponent コンポーネントは、ユーザーをログアウトする方法を示しています。 一般的なアプリでは、ユーザーがアプリにログインしている場合にのみ、親コンポーネント内の LogoutComponent コンポーネントが表示されます。

Shared/LogoutComponent.razor:

@inject AuthenticationStateProvider AuthenticationStateProvider

<button @onclick="Logout">Log out</button>

@code
{
    public async Task Logout()
    {
        await ((ExternalAuthStateProvider)AuthenticationStateProvider)
            .Logout();
    }
}

他の認証情報へのアクセス

Blazor には、Web API への HTTP 要求に使うアクセス トークンなど、他の資格情報を処理する抽象化は定義されていません。 identity プロバイダーのガイダンスに従って、identity プロバイダーの SDK が提供するプリミティブを使ってユーザーの資格情報を管理することをお勧めします。

identity プロバイダーの SDK は、デバイスに格納されたユーザーの資格情報に対してトークン ストアを使うのが一般的です。 SDK のトークン ストアのプリミティブがサービス コンテナーに追加されている場合、アプリ内では SDK のプリミティブを使います。

Blazor フレームワークはユーザーの認証資格情報を認識しておらず、資格情報を一切操作しないので、アプリのコードでは、自分にとって最も便利な任意の方法を実行できます。 ただし、アプリに認証コードを実装する場合は、次のセクション「その他の認証セキュリティに関する考慮事項」の一般的なセキュリティ ガイダンスに従ってください。

その他の認証セキュリティに関する考慮事項

認証プロセスは Blazor の外部にあるので、開発者には、追加のセキュリティ ガイダンスとして identity プロバイダーのガイダンスにアクセスすることをお勧めします。

認証を実装する場合:

  • Web View のコンテキストでの認証を避けます。 たとえば、認証フローを実行するために JavaScript OAuth ライブラリを使うことは避けます。 シングルページ アプリの場合、JavaScript では認証トークンが隠されていないので、悪意のあるユーザーが簡単に見つけて、悪意のある目的で使う可能性があります。 ネイティブ アプリにはこのリスクがありません。なぜなら、ネイティブ アプリはブラウザーのコンテキスト外でのみトークンを取得できるからです。つまり、不正なサードパーティ製スクリプトがトークンを盗んでアプリを侵害することはできません。
  • 認証ワークフローを自分で実装することは避けます。 ほとんどの場合、プラットフォーム ライブラリでは、ハイジャックされる可能性があるカスタムの Web View を使わずに、システムのブラウザーを使って認証ワークフローを安全に処理できます。
  • 認証を実行するためにプラットフォームの Web View コントロールは使わないでください。 代わりに、可能な限りシステムのブラウザーを利用します。
  • ドキュメント コンテキスト (JavaScript) にトークンを渡さないでください。 状況によっては、ドキュメント内の JavaScript ライブラリで、認可された呼び出しを外部サービスに対して実行する必要があります。 JS 相互運用を介して JavaScript からトークンを使用できるようにするのではなく、次のようにします。
    • 生成された一時的なトークンをライブラリと Web View 内に提供します。
    • コード内の送信ネットワーク要求をインターセプトします。
    • 一時的なトークンを実際のトークンに置き換え、要求の宛先が有効であることを確認します。

その他のリソース

Blazor Hybrid アプリの認証は、ネイティブ プラットフォーム ライブラリによって処理されます。これは、ブラウザー サンドボックスでは提供できない拡張セキュリティ保証を提供するためです。 ネイティブ アプリの認証は、OS 固有のメカニズムを使うか、OpenID Connect (OIDC) のようなフェデレーション プロトコルを介して行われます。 アプリ用に選んだ identity プロバイダーのガイダンスに従い、さらにこの記事のガイダンスを使って identity を Blazor と統合します。

認証を統合するには、Razor コンポーネントとサービスについて以下の目標を達成する必要があります。

  • Microsoft.AspNetCore.Components.Authorization パッケージ内で抽象化を使います (AuthorizeView など)。
  • 認証コンテキストの変更に対応します。
  • 認可された API 呼び出しを実行するアクセス トークンなど、identity プロバイダーからアプリによってプロビジョニングされた資格情報にアクセスします。

.NET MAUI、WPF、または Windows フォーム アプリに認証を追加し、ユーザーが正常にログインおよびログアウトできるようになったら、認証を Blazor と統合して、認証済みユーザーが Razor コンポーネントおよびサービスで使用できるようにします。 次の手順に従います。

  • Microsoft.AspNetCore.Components.Authorization パッケージを参照します。

    Note

    .NET アプリへのパッケージの追加に関するガイダンスについては、「パッケージ利用のワークフロー」 (NuGet ドキュメント) の "パッケージのインストールと管理" に関する記事を参照してください。 NuGet.org で正しいパッケージ バージョンを確認します。

  • カスタムの AuthenticationStateProvider を実装します。これは、認証済みユーザーに関する情報にアクセスし、認証状態が変化したときに更新を受け取る目的で Razor コンポーネントが使う抽象化です。

  • 依存関係挿入コンテナーにカスタムの認証状態プロバイダーを登録します。

.NET MAUI アプリは Xamarin.Essentials: Web Authenticator を使います。WebAuthenticator クラスにより、アプリに登録された特定の URL へのコールバックをリッスンするブラウザーベースの認証フローをアプリから開始できます。

その他のガイダンスについては、次のリソースを参照してください。

Microsoft Entra (ME-ID) および AAD B2C と統合するために、Windows フォーム アプリは Microsoft identity プラットフォームを使います。 詳細については、「Microsoft Authentication Library (MSAL) の概要」を参照してください。

ユーザー変更の更新がないカスタムの AuthenticationStateProvider を作成する

アプリの起動直後にアプリがユーザーを認証し、認証済みユーザーがアプリの有効期間全体にわたって同じままである場合、ユーザー変更通知は不要であり、アプリからは認証済みユーザーに関する情報のみが提供されます。 このシナリオでは、アプリを開いたときにユーザーがログインし、ユーザーがログアウトすると、アプリのログイン画面が再び表示されます。次の ExternalAuthStateProvider は、この認証シナリオに対応するカスタムの AuthenticationStateProvider の実装例です。

注意

次のカスタムの AuthenticationStateProvider では、コード例を任意の Blazor Hybrid アプリに適用できるように、名前空間を宣言していません。 ただし、運用アプリでこの例を実装する際には、アプリの名前空間を指定することがベスト プラクティスです。

ExternalAuthStateProvider.cs:

using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components.Authorization;

public class ExternalAuthStateProvider : AuthenticationStateProvider
{
    private readonly Task<AuthenticationState> authenticationState;

    public ExternalAuthStateProvider(AuthenticatedUser user) => 
        authenticationState = Task.FromResult(new AuthenticationState(user.Principal));

    public override Task<AuthenticationState> GetAuthenticationStateAsync() =>
        authenticationState;
}

public class AuthenticatedUser
{
    public ClaimsPrincipal Principal { get; set; } = new();
}

以下の手順では、次の方法について説明します。

  • 必要な名前空間を追加します。
  • 認可サービスと Blazor 抽象化をサービス コレクションに追加します。
  • サービス コレクションを構築します。
  • 認証済みユーザーの要求プリンシパルを設定するために AuthenticatedUser サービスを解決します。 詳細については、identity プロバイダーのドキュメントをご覧ください。
  • 構築したホストを返します。

MauiProgram.csMauiProgram.CreateMauiApp メソッドに、Microsoft.AspNetCore.Components.AuthorizationSystem.Security.Claims の名前空間を追加します。

using Microsoft.AspNetCore.Components.Authorization;
using System.Security.Claims;

構築した Microsoft.Maui.Hosting.MauiAppを返す次のコード行を削除します。

- return builder.Build();

前のコード行を、次のコードで置き換えます。 ユーザーを認証するために OpenID/MSAL コードを追加します。 詳細については、identity プロバイダーのドキュメントをご覧ください。

builder.Services.AddAuthorizationCore();
builder.Services.AddScoped<AuthenticationStateProvider, ExternalAuthStateProvider>();
builder.Services.AddSingleton<AuthenticatedUser>();
var host = builder.Build();

var authenticatedUser = host.Services.GetRequiredService<AuthenticatedUser>();

/*
Provide OpenID/MSAL code to authenticate the user. See your identity provider's 
documentation for details.

The user is represented by a new ClaimsPrincipal based on a new ClaimsIdentity.
*/
var user = new ClaimsPrincipal(new ClaimsIdentity());

authenticatedUser.Principal = user;

return host;

以下の手順では、次の方法について説明します。

  • 必要な名前空間を追加します。
  • 認可サービスと Blazor 抽象化をサービス コレクションに追加します。
  • サービス コレクションを構築し、構築したサービス コレクションをアプリの ResourceDictionary にリソースとして追加します。
  • 認証済みユーザーの要求プリンシパルを設定するために AuthenticatedUser サービスを解決します。 詳細については、identity プロバイダーのドキュメントをご覧ください。
  • 構築したホストを返します。

MainWindow のコンストラクター (MainWindow.xaml.cs) に Microsoft.AspNetCore.Components.AuthorizationSystem.Security.Claims の名前空間を追加します。

using Microsoft.AspNetCore.Components.Authorization;
using System.Security.Claims;

構築したサービス コレクションをアプリの ResourceDictionary にリソースとして追加する次のコード行を削除します。

- Resources.Add("services", serviceCollection.BuildServiceProvider());

前のコード行を、次のコードで置き換えます。 ユーザーを認証するために OpenID/MSAL コードを追加します。 詳細については、identity プロバイダーのドキュメントをご覧ください。

serviceCollection.AddAuthorizationCore();
serviceCollection.AddScoped<AuthenticationStateProvider, ExternalAuthStateProvider>();
serviceCollection.AddSingleton<AuthenticatedUser>();
var services = serviceCollection.BuildServiceProvider();
Resources.Add("services", services);

var authenticatedUser = services.GetRequiredService<AuthenticatedUser>();

/*
Provide OpenID/MSAL code to authenticate the user. See your identity provider's 
documentation for details.

The user is represented by a new ClaimsPrincipal based on a new ClaimsIdentity.
*/
var user = new ClaimsPrincipal(new ClaimsIdentity());

authenticatedUser.Principal = user;

以下の手順では、次の方法について説明します。

  • 必要な名前空間を追加します。
  • 認可サービスと Blazor 抽象化をサービス コレクションに追加します。
  • サービス コレクションを構築し、構築したサービス コレクションをアプリのサービス プロバイダーに追加します。
  • 認証済みユーザーの要求プリンシパルを設定するために AuthenticatedUser サービスを解決します。 詳細については、identity プロバイダーのドキュメントをご覧ください。

Form1 のコンストラクター (Form1.cs) に Microsoft.AspNetCore.Components.AuthorizationSystem.Security.Claims の名前空間を追加します。

using Microsoft.AspNetCore.Components.Authorization;
using System.Security.Claims;

構築したサービス コレクションをアプリのサービス プロバイダーに設定する次のコード行を削除します。

- blazorWebView1.Services = services.BuildServiceProvider();

前のコード行を、次のコードで置き換えます。 ユーザーを認証するために OpenID/MSAL コードを追加します。 詳細については、identity プロバイダーのドキュメントをご覧ください。

services.AddAuthorizationCore();
services.AddScoped<AuthenticationStateProvider, ExternalAuthStateProvider>();
services.AddSingleton<AuthenticatedUser>();
var serviceCollection = services.BuildServiceProvider();
blazorWebView1.Services = serviceCollection;

var authenticatedUser = serviceCollection.GetRequiredService<AuthenticatedUser>();

/*
Provide OpenID/MSAL code to authenticate the user. See your identity provider's 
documentation for details.

The user is represented by a new ClaimsPrincipal based on a new ClaimsIdentity.
*/
var user = new ClaimsPrincipal(new ClaimsIdentity());

authenticatedUser.Principal = user;

ユーザー変更の更新があるカスタムの AuthenticationStateProvider を作成する

Blazor アプリの実行中にユーザーを更新するには、AuthenticationStateProvider の実装内で、次の方法のいずれかを使って NotifyAuthenticationStateChanged を呼び出します。

BlazorWebView の外部から認証の更新を通知する (オプション 1)

カスタムの AuthenticationStateProvider からは、グローバル サービスを使って認証の更新を通知することができます。 AuthenticationStateProvider がサブスクライブし、そのイベントから NotifyAuthenticationStateChanged を呼び出すことができるイベントをサービスで用意することをお勧めします。

注意

次のカスタムの AuthenticationStateProvider では、コード例を任意の Blazor Hybrid アプリに適用できるように、名前空間を宣言していません。 ただし、運用アプリでこの例を実装する際には、アプリの名前空間を指定することがベスト プラクティスです。

ExternalAuthStateProvider.cs:

using System;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components.Authorization;

public class ExternalAuthStateProvider : AuthenticationStateProvider
{
    private AuthenticationState currentUser;

    public ExternalAuthStateProvider(ExternalAuthService service)
    {
        currentUser = new AuthenticationState(service.CurrentUser);

        service.UserChanged += (newUser) =>
        {
            currentUser = new AuthenticationState(newUser);
            NotifyAuthenticationStateChanged(Task.FromResult(currentUser));
        };
    }

    public override Task<AuthenticationState> GetAuthenticationStateAsync() =>
        Task.FromResult(currentUser);
}

public class ExternalAuthService
{
    public event Action<ClaimsPrincipal>? UserChanged;
    private ClaimsPrincipal? currentUser;

    public ClaimsPrincipal CurrentUser
    {
        get { return currentUser ?? new(); }
        set
        {
            currentUser = value;

            if (UserChanged is not null)
            {
                UserChanged(currentUser);
            }
        }
    }
}

MauiProgram.csMauiProgram.CreateMauiApp メソッドで、Microsoft.AspNetCore.Components.Authorization の名前空間を追加します。

using Microsoft.AspNetCore.Components.Authorization;

認可サービスと Blazor 抽象化をサービス コレクションに追加します。

builder.Services.AddAuthorizationCore();
builder.Services.AddScoped<AuthenticationStateProvider, ExternalAuthStateProvider>();
builder.Services.AddSingleton<ExternalAuthService>();

MainWindow のコンストラクター (MainWindow.xaml.cs) に Microsoft.AspNetCore.Components.Authorization の名前空間を追加します。

using Microsoft.AspNetCore.Components.Authorization;

認可サービスと Blazor 抽象化をサービス コレクションに追加します。

serviceCollection.AddAuthorizationCore();
serviceCollection.AddScoped<AuthenticationStateProvider, ExternalAuthStateProvider>();
serviceCollection.AddSingleton<ExternalAuthService>();

Form1 のコンストラクター (Form1.cs) に Microsoft.AspNetCore.Components.Authorization の名前空間を追加します。

using Microsoft.AspNetCore.Components.Authorization;

認可サービスと Blazor 抽象化をサービス コレクションに追加します。

services.AddAuthorizationCore();
services.AddScoped<AuthenticationStateProvider, ExternalAuthStateProvider>();
services.AddSingleton<ExternalAuthService>();

アプリがユーザーを認証するたびに、ExternalAuthService サービスを解決します。

var authService = host.Services.GetRequiredService<ExternalAuthService>();

ユーザーを認証するには、カスタムの OpenID/MSAL コードを実行します。 詳細については、identity プロバイダーのドキュメントをご覧ください。 認証済みユーザー (次の例の authenticatedUser) は、新しい ClaimsIdentity に基づく新しい ClaimsPrincipal です。

現在のユーザーを認証済みユーザーに設定します。

authService.CurrentUser = authenticatedUser;

先ほどの方法の代替案として、サービス経由で設定するのではなく、System.Threading.Thread.CurrentPrincipal に対してユーザーのプリンシパルを設定することができます。その結果、依存関係挿入コンテナーの使用は回避されます。

public class CurrentThreadUserAuthenticationStateProvider : AuthenticationStateProvider
{
    public override Task<AuthenticationState> GetAuthenticationStateAsync() =>
        Task.FromResult(
            new AuthenticationState(Thread.CurrentPrincipal as ClaimsPrincipal ?? 
                new ClaimsPrincipal(new ClaimsIdentity())));
}

この代替方法を使うと、認可サービス (AddAuthorizationCore) と CurrentThreadUserAuthenticationStateProvider (.AddScoped<AuthenticationStateProvider, CurrentThreadUserAuthenticationStateProvider>()) のみがサービス コレクションに追加されます。

BlazorWebView 内で認証を処理する (オプション 2)

カスタムの AuthenticationStateProvider には、ログインとログアウトをトリガーし、ユーザーを更新する追加のメソッドを含めることができます。

注意

次のカスタムの AuthenticationStateProvider では、コード例を任意の Blazor Hybrid アプリに適用できるように、名前空間を宣言していません。 ただし、運用アプリでこの例を実装する際には、アプリの名前空間を指定することがベスト プラクティスです。

ExternalAuthStateProvider.cs:

using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components.Authorization;

public class ExternalAuthStateProvider : AuthenticationStateProvider
{
    private ClaimsPrincipal currentUser = new ClaimsPrincipal(new ClaimsIdentity());

    public override Task<AuthenticationState> GetAuthenticationStateAsync() =>
        Task.FromResult(new AuthenticationState(currentUser));

    public Task LogInAsync()
    {
        var loginTask = LogInAsyncCore();
        NotifyAuthenticationStateChanged(loginTask);

        return loginTask;

        async Task<AuthenticationState> LogInAsyncCore()
        {
            var user = await LoginWithExternalProviderAsync();
            currentUser = user;

            return new AuthenticationState(currentUser);
        }
    }

    private Task<ClaimsPrincipal> LoginWithExternalProviderAsync()
    {
        /*
            Provide OpenID/MSAL code to authenticate the user. See your identity 
            provider's documentation for details.

            Return a new ClaimsPrincipal based on a new ClaimsIdentity.
        */
        var authenticatedUser = new ClaimsPrincipal(new ClaimsIdentity());

        return Task.FromResult(authenticatedUser);
    }

    public void Logout()
    {
        currentUser = new ClaimsPrincipal(new ClaimsIdentity());
        NotifyAuthenticationStateChanged(
            Task.FromResult(new AuthenticationState(currentUser)));
    }
}

前の例の場合:

  • LogInAsyncCore を呼び出すと、ログイン プロセスがトリガーされます。
  • NotifyAuthenticationStateChanged を呼び出すと、更新が進行中であることが通知されます。これにより、アプリはログインまたはログアウト プロセス中に一時的な UI を提供できます。
  • loginTask を返すと、タスクが戻ります。そのため、ログインをトリガーしたコンポーネントは待機し、タスクの完了後に対応することができます。
  • LoginWithExternalProviderAsync メソッドは、開発者が identity プロバイダーの SDK を使ってユーザーをログインするために実装します。 詳細については、identity プロバイダのドキュメントをご覧ください。 認証済みユーザー (authenticatedUser) は、新しい ClaimsIdentity に基づいた新しい ClaimsPrincipal です。

MauiProgram.csMauiProgram.CreateMauiApp メソッドで、認可サービスと Blazor 抽象化をサービス コレクションに追加します。

builder.Services.AddAuthorizationCore();
builder.Services.AddScoped<AuthenticationStateProvider, ExternalAuthStateProvider>();

MainWindow のコンストラクター (MainWindow.xaml.cs) で、認可サービスと Blazor 抽象化をサービス コレクションに追加します。

serviceCollection.AddAuthorizationCore();
serviceCollection.AddScoped<AuthenticationStateProvider, ExternalAuthStateProvider>();

Form1 のコンストラクター (Form1.cs) で、認可サービスと Blazor 抽象化をサービス コレクションに追加します。

services.AddAuthorizationCore();
services.AddScoped<AuthenticationStateProvider, ExternalAuthStateProvider>();

次の LoginComponent コンポーネントは、ユーザーをログインする方法を示しています。 一般的なアプリでは、ユーザーがアプリにログインしていない場合にのみ、親コンポーネント内の LoginComponent コンポーネントが表示されます。

Shared/LoginComponent.razor:

@inject AuthenticationStateProvider AuthenticationStateProvider

<button @onclick="Login">Log in</button>

@code
{
    public async Task Login()
    {
        await ((ExternalAuthStateProvider)AuthenticationStateProvider)
            .LogInAsync();
    }
}

次の LogoutComponent コンポーネントは、ユーザーをログアウトする方法を示しています。 一般的なアプリでは、ユーザーがアプリにログインしている場合にのみ、親コンポーネント内の LogoutComponent コンポーネントが表示されます。

Shared/LogoutComponent.razor:

@inject AuthenticationStateProvider AuthenticationStateProvider

<button @onclick="Logout">Log out</button>

@code
{
    public async Task Logout()
    {
        await ((ExternalAuthStateProvider)AuthenticationStateProvider)
            .Logout();
    }
}

他の認証情報へのアクセス

Blazor には、Web API への HTTP 要求に使うアクセス トークンなど、他の資格情報を処理する抽象化は定義されていません。 identity プロバイダーのガイダンスに従って、identity プロバイダーの SDK が提供するプリミティブを使ってユーザーの資格情報を管理することをお勧めします。

identity プロバイダーの SDK は、デバイスに格納されたユーザーの資格情報に対してトークン ストアを使うのが一般的です。 SDK のトークン ストアのプリミティブがサービス コンテナーに追加されている場合、アプリ内では SDK のプリミティブを使います。

Blazor フレームワークはユーザーの認証資格情報を認識しておらず、資格情報を一切操作しないので、アプリのコードでは、自分にとって最も便利な任意の方法を実行できます。 ただし、アプリに認証コードを実装する場合は、次のセクション「その他の認証セキュリティに関する考慮事項」の一般的なセキュリティ ガイダンスに従ってください。

その他の認証セキュリティに関する考慮事項

認証プロセスは Blazor の外部にあるので、開発者には、追加のセキュリティ ガイダンスとして identity プロバイダーのガイダンスにアクセスすることをお勧めします。

認証を実装する場合:

  • Web View のコンテキストでの認証を避けます。 たとえば、認証フローを実行するために JavaScript OAuth ライブラリを使うことは避けます。 シングルページ アプリの場合、JavaScript では認証トークンが隠されていないので、悪意のあるユーザーが簡単に見つけて、悪意のある目的で使う可能性があります。 ネイティブ アプリにはこのリスクがありません。なぜなら、ネイティブ アプリはブラウザーのコンテキスト外でのみトークンを取得できるからです。つまり、不正なサードパーティ製スクリプトがトークンを盗んでアプリを侵害することはできません。
  • 認証ワークフローを自分で実装することは避けます。 ほとんどの場合、プラットフォーム ライブラリでは、ハイジャックされる可能性があるカスタムの Web View を使わずに、システムのブラウザーを使って認証ワークフローを安全に処理できます。
  • 認証を実行するためにプラットフォームの Web View コントロールは使わないでください。 代わりに、可能な限りシステムのブラウザーを利用します。
  • ドキュメント コンテキスト (JavaScript) にトークンを渡さないでください。 状況によっては、ドキュメント内の JavaScript ライブラリで、認可された呼び出しを外部サービスに対して実行する必要があります。 JS 相互運用を介して JavaScript からトークンを使用できるようにするのではなく、次のようにします。
    • 生成された一時的なトークンをライブラリと Web View 内に提供します。
    • コード内の送信ネットワーク要求をインターセプトします。
    • 一時的なトークンを実際のトークンに置き換え、要求の宛先が有効であることを確認します。

その他のリソース