ホストされている ASP.NET Core Blazor WebAssembly アプリを Identity Server でセキュリティ保護する

この記事では、ユーザーと API 呼び出しの認証に IdentityServer を使用する、ホストされている Blazor WebAssembly ソリューションを作成する方法について説明します。

重要

Duende Software により、Duende Identity Server を実稼働で使用することのライセンス料の支払いが求められる場合があります。 詳細については、「ASP.NET Core 5.0 から 6.0 への移行」を参照してください。

Note

既存の外部 Identity サーバー インスタンスを使用するように、スタンドアロンの、またはホストされた Blazor WebAssembly アプリを構成するには、「認証ライブラリを使用して、ASP.NET Core Blazor WebAssembly スタンドアロン アプリをセキュリティで保護する」のガイダンスに従ってください。

この記事を読んだ後の追加のセキュリティ シナリオの説明については、「ASP.NET Core Blazor WebAssembly のセキュリティに関するその他のシナリオ」を参照してください。

チュートリアル

このチュートリアルのサブセクションでは、次の方法について説明します。

  • Blazor アプリを作成する
  • アプリを実行する

Blazor アプリを作成する

認証メカニズムを使用して新しい Blazor WebAssembly プロジェクトを作成するには:

  1. 新しいプロジェクトを作成します。

  2. Blazor WebAssembly アプリ テンプレートを選択します。 [次へ] を選択します。

  3. ダッシュを使用せずにプロジェクト名を指定します。 場所が正しいことを確認します。 [次へ] を選択します。

    OIDC アプリ識別子の形成を妨げるダッシュ (-) をプロジェクト名に使用しないでください。 Blazor WebAssembly プロジェクト テンプレート内のロジックでは、ソリューションの構成内の OIDC アプリ識別子にプロジェクト名を使用します。OIDC アプリ識別子内にダッシュは許可されません。 パスカル ケース (BlazorSample) またはアンダースコア (Blazor_Sample) は許容可能な代替手段です

  4. [追加情報] ダイアログで、[認証の種類] として [個別のアカウント] を選択し、ASP.NET Core の Identity システムを使用してアプリ内にユーザーを格納します。

  5. [ASP.NET Core hosted](ASP.NET Core ホステッド) チェックボックスをオンにします。

  6. [作成] ボタンを選択してアプリを作成します。

アプリを実行する

Server プロジェクトからアプリを実行します。 Visual Studio を使用しているときは、次のいずれかを行います。

  • [実行] ボタンの横にあるドロップダウン矢印を選択します。 ドロップダウン リストから [Configure Startup Projects] (スタートアップ プロジェクトの構成) を開きます。 [シングル スタートアップ プロジェクト] オプションを選択します。 スタートアップ プロジェクトのプロジェクトを確認するか、Server プロジェクトに変更します。

  • 次のいずれかの方法でアプリを起動する前に、Server プロジェクトがソリューション エクスプローラーで強調表示されていることを確認します。

    • [実行] ボタンを選択します。
    • メニューの、 [デバッグ]>[デバッグ開始] を使用します。
    • F5キーを押します。
  • コマンド シェルで、ソリューションの Server プロジェクト フォルダーに移動します。 dotnet run コマンドを実行します。

ソリューションの各パーツ

このセクションでは、Blazor WebAssembly プロジェクト テンプレートから生成されたソリューションの各部分について説明し、そのソリューションの Client および Server プロジェクトを参照用に構成する方法について説明します。 「チュートリアル」セクションのガイダンスを使用してアプリを作成した場合、基本的な作業アプリケーションについては、このセクションに従う必要がある具体的なガイダンスはありません。 このセクションのガイダンスは、ユーザーの認証と承認を行うためにアプリを更新する場合に役立ちます。 ただし、アプリは別の方法で更新することもできます。それには、「チュートリアル」セクションに記載のガイダンスに従って新しいアプリを作成し、アプリのコンポーネント、クラス、リソースを新しいアプリに移動してください。

Server アプリ サービス

"このセクションは、ソリューションの Server アプリに関連しています。 "

次のサービスが登録されます。

  • Program ファイルで次の操作を行います。

    • Entity Framework Core と ASP.NET Core Identity:

      builder.Services.AddDbContext<ApplicationDbContext>(options =>
          options.UseSqlite( ... ));
      builder.Services.AddDatabaseDeveloperPageExceptionFilter();
      
      builder.Services.AddDefaultIdentity<ApplicationUser>(options => 
              options.SignIn.RequireConfirmedAccount = true)
          .AddEntityFrameworkStores<ApplicationDbContext>();
      
    • Identity Server 上に既定の ASP.NET Core 規則を設定する AddApiAuthorization ヘルパー メソッドが追加された Identity Server:

      builder.Services.AddIdentityServer()
          .AddApiAuthorization<ApplicationUser, ApplicationDbContext>();
      
    • Identity Server によって生成された JWT トークンを検証するようにアプリを構成する AddIdentityServerJwt ヘルパー メソッドが追加された Authentication:

      builder.Services.AddAuthentication()
          .AddIdentityServerJwt();
      
  • Startup.csStartup.ConfigureServices で:

    • Entity Framework Core と ASP.NET Core Identity:

      services.AddDbContext<ApplicationDbContext>(options =>
          options.UseSqlite(
              Configuration.GetConnectionString("DefaultConnection")));
      
      services.AddDefaultIdentity<ApplicationUser>(options => 
              options.SignIn.RequireConfirmedAccount = true)
          .AddEntityFrameworkStores<ApplicationDbContext>();
      
    • Identity Server 上に既定の ASP.NET Core 規則を設定する AddApiAuthorization ヘルパー メソッドが追加された Identity Server:

      services.AddIdentityServer()
          .AddApiAuthorization<ApplicationUser, ApplicationDbContext>();
      
    • Identity Server によって生成された JWT トークンを検証するようにアプリを構成する AddIdentityServerJwt ヘルパー メソッドが追加された Authentication:

      services.AddAuthentication()
          .AddIdentityServerJwt();
      

注意

1 つの認証スキームが登録されると、認証スキームがアプリの既定のスキームとして自動的に使用され、スキームを AddAuthentication に指定、または AuthenticationOptions 経由にする必要はありません。 詳細については、「ASP.NET Core の認証の概要」と ASP.NET Core のお知らせ (aspnet/Announcements #490) を参照してください。

  • Program ファイル:
  • Startup.csStartup.Configure で:
  • Identity Server ミドルウェアによって、OpenID Connect (OIDC) のエンドポイントが公開されます。

    app.UseIdentityServer();
    
  • Authentication ミドルウェアによって、要求の資格情報の検証と、要求コンテキストでのユーザーの設定が行われます。

    app.UseAuthentication();
    
  • 承認ミドルウェアにより、承認機能が有効になります。

    app.UseAuthorization();
    

API 認可

"このセクションは、ソリューションの Server アプリに関連しています。 "

AddApiAuthorization ヘルパー メソッドでは、ASP.NET Core シナリオ用に Identity Server を構成します。 Identity Server は、アプリのセキュリティの問題を処理するための強力で拡張可能なフレームワークです。 Identity Server を使用すると、ほとんどの一般的なシナリオには必要のない複雑さが発生します。 そのため、使用開始時に適切であると考えられる一連の規則と構成オプションが用意されています。 認証のニーズが変わったら、Identity Server のあらゆる機能を利用し、アプリの要件に合わせて認証をカスタマイズできます。

Identity Server と共存する API の認証ハンドラーを追加する

"このセクションは、ソリューションの Server アプリに関連しています。 "

AddIdentityServerJwt ヘルパー メソッドでは、アプリに対するポリシー スキームが既定の認証ハンドラーとして構成されます。 そのポリシーは、Identity の URL 空間の /Identity 以下のサブパスにルーティングされたすべての要求を Identity で処理できるように構成されています。 それ以外のすべての要求は、JwtBearerHandler で処理されます。 さらに、このメソッドでは次のことが行われます。

  • 既定のスコープ {PROJECT NAME}API を使用して Identity Server に API リソースを登録します。ここで、{PROJECT NAME} プレースホルダーはアプリ作成時のプロジェクトの名前です。
  • Identity Server によってアプリに対して発行されたトークンを検証するように、JWT ベアラー トークン ミドルウェアを構成します。

天気予報コントローラー

"このセクションは、ソリューションの Server アプリに関連しています。 "

WeatherForecastController (Controllers/WeatherForecastController.cs) では、[Authorize] 属性がクラスに適用されます。 その属性では、ユーザーはリソースにアクセスするために既定のポリシーに基づいて承認される必要があることが示されています。 既定の承認ポリシーは、AddIdentityServerJwt によって設定される既定の認証スキームを使用するように構成されています。 ヘルパー メソッドでは、アプリへの要求に対する既定のハンドラーとして JwtBearerHandler が構成されます。

アプリケーション データベース コンテキスト

"このセクションは、ソリューションの Server アプリに関連しています。 "

ApplicationDbContext (Data/ApplicationDbContext.cs) では、DbContext により、Identity Server 用のスキーマを含むように ApiAuthorizationDbContext<TUser> が拡張されます。 ApiAuthorizationDbContext<TUser> は、IdentityDbContext から派生しています。

データベース スキーマを完全に制御するには、使用可能な IdentityDbContext クラスの 1 つを継承し、OnModelCreating メソッドで builder.ConfigurePersistedGrantContext(_operationalStoreOptions.Value) を呼び出すことによって、Identity スキーマを含むようにコンテキストを構成します。

OIDC 構成コントローラー

"このセクションは、ソリューションの Server アプリに関連しています。 "

OidcConfigurationController (Controllers/OidcConfigurationController.cs) では、OIDC パラメーターを提供するために、クライアント エンドポイントがプロビジョニングされます。

アプリの設定

"このセクションは、ソリューションの Server アプリに関連しています。 "

プロジェクト ルートにあるアプリ設定ファイル (appsettings.json) の IdentityServer セクションには、構成されているクライアントの一覧が記述されてます。 次の例には、1 つのクライアントがあります。 クライアント名は Client アプリのアセンブリ名に対応し、規則によって OAuth の ClientId パラメーターにマップされます。 構成対象のアプリの種類は、プロファイルによって示されています。 プロファイルは、サーバーの構成プロセスを簡素化する規則を促進するために、内部的に使用されます。

"IdentityServer": {
  "Clients": {
    "{ASSEMBLY NAME}": {
      "Profile": "IdentityServerSPA"
    }
  }
}

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

認証パッケージ

"このセクションは、ソリューションの Client アプリに関連しています。 "

個人のユーザー アカウント (Individual) を使用するようにアプリを作成すると、アプリは Microsoft.AspNetCore.Components.WebAssembly.Authentication パッケージのパッケージ参照を自動的に受け取ります。 このパッケージには、アプリでユーザーを認証し、保護された API を呼び出すためのトークンを取得するのに役立つ一連のプリミティブが用意されています。

アプリに認証を追加する場合は、アプリに Microsoft.AspNetCore.Components.WebAssembly.Authentication パッケージを手動で追加します。

Note

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

HttpClient 構成

"このセクションは、ソリューションの Client アプリに関連しています。 "

Program ファイルでは、サーバー API への要求を行うときのアクセス トークンが含まれる HttpClient インスタンスを提供するように、名前付きの HttpClient が構成されます。 ソリューションの作成時、名前付きの HttpClient は既定で {PROJECT NAME}.ServerAPI になります。ここで、{PROJECT NAME} プレースホルダーはプロジェクトの名前です。

builder.Services.AddHttpClient("{PROJECT NAME}.ServerAPI", 
        client => client.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress))
    .AddHttpMessageHandler<BaseAddressAuthorizationMessageHandler>();

builder.Services.AddScoped(sp => sp.GetRequiredService<IHttpClientFactory>()
    .CreateClient("{PROJECT NAME}.ServerAPI"));

プレースホルダー {PROJECT NAME} は、ソリューション作成時のプロジェクト名です。 たとえば、プロジェクト名として BlazorSample を指定すると、BlazorSample.ServerAPI の名前付きの HttpClient が生成されます。

Note

ホストされている Blazorソリューションの一部ではない既存の Identity Server インスタンスを使用するように Blazor WebAssembly アプリを構成する場合は、HttpClient ベース アドレスの登録を IWebAssemblyHostEnvironment.BaseAddress (builder.HostEnvironment.BaseAddress) からサーバー アプリの API 認証エンドポイント URL に変更します。

API の承認のサポート

"このセクションは、ソリューションの Client アプリに関連しています。 "

ユーザーの認証に対するサポートは、Microsoft.AspNetCore.Components.WebAssembly.Authentication パッケージ内で提供される拡張メソッドによって、サービス コンテナーに接続されます。 このメソッドでは、アプリが既存の承認システムとやり取りするために必要なサービスが設定されます。

builder.Services.AddApiAuthorization();

既定では、アプリの構成は規則により _configuration/{client-id} から読み込まれます。 規則により、アプリのアセンブリ名にはクライアント ID が設定されます。 オプションを指定してオーバーロードを呼び出すことにより、別のエンドポイントを指すようにこの URL を変更できます。

Imports ファイル

"このセクションは、ソリューションの Client アプリに関連しています。 "

Microsoft.AspNetCore.Components.Authorization 名前空間は、_Imports.razor ファイルを介してアプリ全体で使用できるようになります。

@using System.Net.Http
@using System.Net.Http.Json
@using Microsoft.AspNetCore.Components.Authorization
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.Web
@using Microsoft.AspNetCore.Components.Web.Virtualization
@using Microsoft.AspNetCore.Components.WebAssembly.Http
@using Microsoft.JSInterop
@using {APPLICATION ASSEMBLY}
@using {APPLICATION ASSEMBLY}.Shared

Index ページ

"このセクションは、ソリューションの Client アプリに関連しています。 "

Index ページ (wwwroot/index.html) ページには、JavaScript で AuthenticationService を定義するスクリプトが含まれています。 AuthenticationService によって、OIDC プロトコルの下位レベルの詳細が処理されます。 アプリは、認証操作を実行するために、スクリプトで定義されているメソッドを内部的に呼び出します。

<script src="_content/Microsoft.AspNetCore.Components.WebAssembly.Authentication/AuthenticationService.js"></script>

App コンポーネント

"このセクションは、ソリューションの Client アプリに関連しています。 "

App コンポーネント (App.razor) は、Blazor Server アプリにある App コンポーネントに似ています。

  • CascadingAuthenticationState コンポーネントによって、アプリの残りの部分に AuthenticationState を公開する動作が管理されます。
  • AuthorizeRouteView コンポーネントによって、現在のユーザーには所与のページへのアクセスが許可されます。それ以外では、RedirectToLogin コンポーネントがレンダリングされます。
  • RedirectToLogin コンポーネントによって、承認されていないユーザーのログイン ページへのリダイレクトが管理されます。

ASP.NET Core のリリースごとにフレームワークに違いがあるため、App コンポーネント (App.razor) の Razor マークアップは、このセクションでは説明しません。 特定のリリース向けのコンポーネントのマークアップを調べる場合は、次の方法の ''いずれか'' を使用してください。

  • 使用しようとしている ASP.NET Core のバージョン向けの既定の Blazor WebAssembly プロジェクト テンプレートから、認証のためにプロビジョニングされたアプリを作成します。 作成されたアプリで、App コンポーネント (App.razor) を検証します。

  • 参照元 で、App コンポーネント (App.razor) を検証します。 ブランチ セレクターからバージョンを選択し、リポジトリの ProjectTemplates フォルダーでコンポーネントを検索します。これは、長年にわたって移動しているためです。

    Note

    通常、.NET 参照ソースへのドキュメント リンクを使用すると、リポジトリの既定のブランチが読み込まれます。このブランチは、.NET の次回リリースに向けて行われている現在の開発を表します。 特定のリリースのタグを選択するには、[Switch branches or tags](ブランチまたはタグの切り替え) ドロップダウン リストを使います。 詳細については、「ASP.NET Core ソース コードのバージョン タグを選択する方法」 (dotnet/AspNetCore.Docs #26205) を参照してください。

RedirectToLogin コンポーネント

"このセクションは、ソリューションの Client アプリに関連しています。 "

RedirectToLogin コンポーネント (RedirectToLogin.razor) は:

  • 承認されていないユーザーのログイン ページへのリダイレクトを管理します。
  • ユーザーがアクセスしようとしている現在の URL は、次を使用して認証が成功した場合にそのページに戻ることができるように保持されます。

参照元で、RedirectToLogin コンポーネントを検証します。 コンポーネントの場所は時間の経過と共に変更されたため、GitHub 検索ツールを使用してコンポーネントを見つけてください。

Note

通常、.NET 参照ソースへのドキュメント リンクを使用すると、リポジトリの既定のブランチが読み込まれます。このブランチは、.NET の次回リリースに向けて行われている現在の開発を表します。 特定のリリースのタグを選択するには、[Switch branches or tags](ブランチまたはタグの切り替え) ドロップダウン リストを使います。 詳細については、「ASP.NET Core ソース コードのバージョン タグを選択する方法」 (dotnet/AspNetCore.Docs #26205) を参照してください。

LoginDisplay コンポーネント

"このセクションは、ソリューションの Client アプリに関連しています。 "

LoginDisplay コンポーネント (LoginDisplay.razor) は MainLayout コンポーネント (MainLayout.razor) でレンダリングされます。このコンポーネントによって次の動作が管理されます。

  • 認証されたユーザーの場合:
    • 現在のユーザー名が表示されます。
    • ASP.NET Core Identity のユーザー プロファイル ページへのリンクが提供されます。
    • アプリからログアウトするためのボタンが用意されます。
  • 匿名ユーザーの場合:
    • 登録するオプションが提供されます。
    • ログインするオプションが提供されます。

ASP.NET Core のリリースごとにフレームワークに違いがあるため、LoginDisplay コンポーネントの Razor マークアップは、このセクションでは説明しません。 特定のリリース向けのコンポーネントのマークアップを調べる場合は、次の方法の ''いずれか'' を使用してください。

  • 使用しようとしている ASP.NET Core のバージョン向けの既定の Blazor WebAssembly プロジェクト テンプレートから、認証のためにプロビジョニングされたアプリを作成します。 作成されたアプリで、LoginDisplay コンポーネントを検証します。

  • 参照元で、LoginDisplay コンポーネントを検証します。 コンポーネントの場所は時間の経過と共に変更されたため、GitHub 検索ツールを使用してコンポーネントを見つけてください。 true と等しい Hosted のテンプレート コンテンツが使用されます。

    注意

    通常、.NET 参照ソースへのドキュメント リンクを使用すると、リポジトリの既定のブランチが読み込まれます。このブランチは、.NET の次回リリースに向けて行われている現在の開発を表します。 特定のリリースのタグを選択するには、[Switch branches or tags](ブランチまたはタグの切り替え) ドロップダウン リストを使います。 詳細については、「ASP.NET Core ソース コードのバージョン タグを選択する方法」 (dotnet/AspNetCore.Docs #26205) を参照してください。

Authentication コンポーネント

"このセクションは、ソリューションの Client アプリに関連しています。 "

Authentication コンポーネント (Pages/Authentication.razor) によって生成されるページによって、さまざまな認証ステージを処理するために必要なルートが定義されます。

RemoteAuthenticatorView コンポーネント:

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

<RemoteAuthenticatorView Action="Action" />

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

Note

null 許容参照型 (NRT) と .NET コンパイラの null 状態スタティック分析は、.NET 6 以降の ASP.NET Core でサポートされています。 .NET 6 の ASP.NET Core のリリースより前は、string 型は null 型の指定 (?) なしで表示されます。

FetchData コンポーネント

"このセクションは、ソリューションの Client アプリに関連しています。 "

FetchData コンポーネントは次の方法を示します。

  • アクセス トークンをプロビジョニングする。
  • アクセス トークンを使用して、Server アプリで保護されたリソース API を呼び出す。

@attribute [Authorize] ディレクティブは、このコンポーネントにアクセスするためにユーザーを承認する必要があることを Blazor WebAssembly の承認システムに示します。 Client アプリに属性が存在しても、適切な資格情報を使用せずにサーバー上の API が呼び出されるのを防ぐことはできません。 また、Server アプリは、適切なエンドポイントでそれを正しく保護するために [Authorize] も使用する必要があります。

IAccessTokenProvider.RequestAccessToken は、API を呼び出すために要求に追加できるアクセス トークンの要求を処理します。 トークンがキャッシュされている場合、またはサービスがユーザーの操作なしで新しいアクセス トークンをプロビジョニングできる場合、トークン要求は成功します。 それ以外の場合、トークン要求は、try-catch ステートメントでキャッチされた AccessTokenNotAvailableException により失敗します。

要求に含める実際のトークンを取得するには、アプリが tokenResult.TryGetToken(out var token) を呼び出して、要求が成功したことを確認する必要があります。

要求が成功すると、トークン変数にアクセス トークンが設定されます。 トークンの AccessToken.Value プロパティは、Authorization 要求ヘッダーに含めるリテラル文字列を公開します。

ユーザーの操作なしでトークンをプロビジョニングできなかったために要求が失敗した場合:

  • .NET 7 以降の ASP.NET Core: アプリは指定された AccessTokenResult.InteractionOptions を使用して AccessTokenResult.InteractiveRequestUrl に移動し、アクセス トークンを更新できるようにします。
  • .NET 6 以前の ASP.NET Core: トークン結果にはリダイレクト URL が含まれます。 この URL に移動すると、認証が成功した後、ユーザーがログイン ページに移動してから、現在のページに戻ります。
@page "/fetchdata"
@using Microsoft.AspNetCore.Authorization
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
@using {APP NAMESPACE}.Shared
@attribute [Authorize]
@inject HttpClient Http

...

@code {
    private WeatherForecast[] forecasts;

    protected override async Task OnInitializedAsync()
    {
        try
        {
            forecasts = await Http.GetFromJsonAsync<WeatherForecast[]>("WeatherForecast");
        }
        catch (AccessTokenNotAvailableException exception)
        {
            exception.Redirect();
        }
    }
}

Azure App Service on Linux

Azure App Service on Linux にデプロイするときに、発行者を明示的に指定します。 詳しくは、「Identity を使用して SPA の Web API バックエンドをセキュリティで保護する方法」を参照してください。

API 認証での名前とロール要求

カスタム ユーザー ファクトリ

Client アプリで、カスタム ユーザー ファクトリを作成します。 Identity Server により、複数のロールが JSON 配列として 1 つの role 要求で送信されます。 1 つのロールは、要求内で文字列値として送信されます。 ファクトリにより、ユーザーのロールごとに個別の role 要求が作成されます。

CustomUserFactory.cs:

using System.Security.Claims;
using System.Text.Json;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication.Internal;

public class CustomUserFactory
    : AccountClaimsPrincipalFactory<RemoteUserAccount>
{
    public CustomUserFactory(IAccessTokenProviderAccessor accessor)
        : base(accessor)
    {
    }

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

        if (user.Identity is not null && user.Identity.IsAuthenticated)
        {
            var identity = (ClaimsIdentity)user.Identity;
            var roleClaims = identity.FindAll(identity.RoleClaimType).ToArray();

            if (roleClaims.Any())
            {
                foreach (var existingClaim in roleClaims)
                {
                    identity.RemoveClaim(existingClaim);
                }

                var rolesElem = 
                    account.AdditionalProperties[identity.RoleClaimType];

                if (options.RoleClaim is not null && rolesElem is JsonElement roles)
                {
                    if (roles.ValueKind == JsonValueKind.Array)
                    {
                        foreach (var role in roles.EnumerateArray())
                        {
                            var roleValue = role.GetString();

                            if (!string.IsNullOrEmpty(roleValue))
                            {
                                identity.AddClaim(
                                  new Claim(options.RoleClaim, roleValue));
                            }

                        }
                    }
                    else
                    {
                        var roleValue = roles.GetString();

                        if (!string.IsNullOrEmpty(roleValue))
                        {
                            identity.AddClaim(
                              new Claim(options.RoleClaim, roleValue));
                        }
                    }
                }
            }
        }

        return user;
    }
}
using System.Linq;
using System.Security.Claims;
using System.Text.Json;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication.Internal;

public class CustomUserFactory
    : AccountClaimsPrincipalFactory<RemoteUserAccount>
{
    public CustomUserFactory(IAccessTokenProviderAccessor accessor)
        : base(accessor)
    {
    }

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

        if (user.Identity.IsAuthenticated)
        {
            var identity = (ClaimsIdentity)user.Identity;
            var roleClaims = identity.FindAll(identity.RoleClaimType).ToArray();

            if (roleClaims.Any())
            {
                foreach (var existingClaim in roleClaims)
                {
                    identity.RemoveClaim(existingClaim);
                }

                var rolesElem = account.AdditionalProperties[identity.RoleClaimType];

                if (rolesElem is JsonElement roles)
                {
                    if (roles.ValueKind == JsonValueKind.Array)
                    {
                        foreach (var role in roles.EnumerateArray())
                        {
                            identity.AddClaim(new Claim(options.RoleClaim, role.GetString()));
                        }
                    }
                    else
                    {
                        identity.AddClaim(new Claim(options.RoleClaim, roles.GetString()));
                    }
                }
            }
        }

        return user;
    }
}

Client アプリでは、Program ファイルにファクトリを登録します。

builder.Services.AddApiAuthorization()
    .AddAccountClaimsPrincipalFactory<CustomUserFactory>();

Server アプリで、Identity ビルダーに AddRoles を呼び出します。これにより、ロールに関連するサービスが追加されます。

Program ファイルでは:

using Microsoft.AspNetCore.Identity;

...

builder.Services.AddDefaultIdentity<ApplicationUser>(options => 
    options.SignIn.RequireConfirmedAccount = true)
    .AddRoles<IdentityRole>()
    .AddEntityFrameworkStores<ApplicationDbContext>();

Startup.cs:

using Microsoft.AspNetCore.Identity;

...

services.AddDefaultIdentity<ApplicationUser>(options => 
    options.SignIn.RequireConfirmedAccount = true)
    .AddRoles<IdentityRole>()
    .AddEntityFrameworkStores<ApplicationDbContext>();

Identity Server を構成する

次の方法のいずれかを使用します。

API 承認のオプション

Server アプリで:

  • name 要求と role 要求を ID トークンとアクセス トークンに挿入するように、Identity Server を構成します。
  • JWT トークン ハンドラーでロールの既定のマッピングを禁止します。

Program ファイルでは:

using System.IdentityModel.Tokens.Jwt;

...

builder.Services.AddIdentityServer()
    .AddApiAuthorization<ApplicationUser, ApplicationDbContext>(options => {
        options.IdentityResources["openid"].UserClaims.Add("name");
        options.ApiResources.Single().UserClaims.Add("name");
        options.IdentityResources["openid"].UserClaims.Add("role");
        options.ApiResources.Single().UserClaims.Add("role");
    });

JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Remove("role");

Startup.cs:

using System.IdentityModel.Tokens.Jwt;
using System.Linq;

...

services.AddIdentityServer()
    .AddApiAuthorization<ApplicationUser, ApplicationDbContext>(options => {
        options.IdentityResources["openid"].UserClaims.Add("name");
        options.ApiResources.Single().UserClaims.Add("name");
        options.IdentityResources["openid"].UserClaims.Add("role");
        options.ApiResources.Single().UserClaims.Add("role");
    });

JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Remove("role");

プロファイル サービス

Server アプリで、ProfileService の実装を作成します。

ProfileService.cs:

using IdentityModel;
using Duende.IdentityServer.Models;
using Duende.IdentityServer.Services;

public class ProfileService : IProfileService
{
    public ProfileService()
    {
    }

    public async Task GetProfileDataAsync(ProfileDataRequestContext context)
    {
        var nameClaim = context.Subject.FindAll(JwtClaimTypes.Name);
        context.IssuedClaims.AddRange(nameClaim);

        var roleClaims = context.Subject.FindAll(JwtClaimTypes.Role);
        context.IssuedClaims.AddRange(roleClaims);

        await Task.CompletedTask;
    }

    public async Task IsActiveAsync(IsActiveContext context)
    {
        await Task.CompletedTask;
    }
}
using IdentityModel;
using Duende.IdentityServer.Models;
using Duende.IdentityServer.Services;
using System.Threading.Tasks;

public class ProfileService : IProfileService
{
    public ProfileService()
    {
    }

    public async Task GetProfileDataAsync(ProfileDataRequestContext context)
    {
        var nameClaim = context.Subject.FindAll(JwtClaimTypes.Name);
        context.IssuedClaims.AddRange(nameClaim);

        var roleClaims = context.Subject.FindAll(JwtClaimTypes.Role);
        context.IssuedClaims.AddRange(roleClaims);

        await Task.CompletedTask;
    }

    public async Task IsActiveAsync(IsActiveContext context)
    {
        await Task.CompletedTask;
    }
}

Server アプリでは、Program ファイルにプロファイル サービスを登録します。

using Duende.IdentityServer.Services;

...

builder.Services.AddTransient<IProfileService, ProfileService>();

JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Remove("role");

Server アプリで、Startup.csStartup.ConfigureServices にプロファイル サービスを登録します。

using IdentityServer4.Services;

...

services.AddTransient<IProfileService, ProfileService>();

JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Remove("role");

承認メカニズムを使用する

Client アプリでは、この時点でコンポーネントの承認方法が機能しています。 コンポーネント内のすべての承認メカニズムで、ロールを使用してユーザーを承認できます。

User.Identity.Name には、 Client アプリにおいてユーザーのユーザー名が設定されます。これは通常、サインイン メール アドレスです。

UserManager および SignInManager

サーバー アプリで次が必要な場合に、ユーザー識別子の要求の種類を設定します。

.NET 6 以降の ASP.NET Core での Program.cs の場合:

using System.Security.Claims;

...

builder.Services.Configure<IdentityOptions>(options => 
    options.ClaimsIdentity.UserIdClaimType = ClaimTypes.NameIdentifier);

6\.0 より前の ASP.NET Core バージョンの Startup.ConfigureServices の場合:

using System.Security.Claims;

...

services.Configure<IdentityOptions>(options => 
    options.ClaimsIdentity.UserIdClaimType = ClaimTypes.NameIdentifier);

次の WeatherForecastController は、Get メソッドが呼び出されたときに UserName をログします。

Note

次の例では、C# 10 以降 (.NET 6 以降) の機能であるファイルスコープ名前空間を使用します。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Logging;
using BlazorSample.Server.Models;
using BlazorSample.Shared;

namespace BlazorSample.Server.Controllers;

[Authorize]
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
    private readonly UserManager<ApplicationUser> userManager;

    private static readonly string[] Summaries = new[]
    {
        "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", 
        "Balmy", "Hot", "Sweltering", "Scorching"
    };

    private readonly ILogger<WeatherForecastController> logger;

    public WeatherForecastController(ILogger<WeatherForecastController> logger, 
        UserManager<ApplicationUser> userManager)
    {
        this.logger = logger;
        this.userManager = userManager;
    }

    [HttpGet]
    public async Task<IEnumerable<WeatherForecast>> Get()
    {
        var rng = new Random();

        var user = await userManager.GetUserAsync(User);

        if (user != null)
        {
            logger.LogInformation("User.Identity.Name: {UserIdentityName}", user.UserName);
        }

        return Enumerable.Range(1, 5).Select(index => new WeatherForecast
        {
            Date = DateTime.Now.AddDays(index),
            TemperatureC = rng.Next(-20, 55),
            Summary = Summaries[rng.Next(Summaries.Length)]
        })
        .ToArray();
    }
}

前の例の場合:

  • Server プロジェクトの名前空間は BlazorSample.Server です。
  • Shared プロジェクトの名前空間は BlazorSample.Shared です。

カスタム ドメインと証明書を使用した Azure App Service でのホスト

以下のガイダンスでは、次について説明します。

  • Identity Server を使用してホストされている Blazor WebAssembly アプリを、カスタム ドメインを使用して Azure App Service にデプロイする方法。
  • ブラウザーとの HTTPS プロトコル通信に対して、TLS 証明書を作成して使用する方法。 このガイダンスではカスタム ドメインで証明書を使用する方法に焦点を当てていますが、contoso.azurewebsites.net などの既定の Azure アプリのドメインを使用する場合にも同様に当てはまります。

このホスティング シナリオの場合、Identity Server のトークン署名キーと、サイトの HTTPS でセキュリティ保護されたブラウザーとの通信に、同じ証明書を使用しないでください

  • この 2 つの要件に対して異なる証明書を使用することは、それぞれの目的に対して秘密キーが分離されるため、優れたセキュリティ方法です。
  • ブラウザーとの通信に使用される TLS 証明書は、Identity Server のトークン署名に影響を与えることなく、個別に管理されます。
  • カスタム ドメイン バインドのために Azure Key Vault で App Service アプリに証明書を提供すると、Identity Server で Azure Key Vault からトークン署名用に同じ証明書を取得することはできません。 物理パスから同じ TLS 証明書を使用するように Identity Server を構成することはできますが、セキュリティ証明書をソース管理に置くことは不適切な方法であり、ほとんどのシナリオで避ける必要があります

次のガイダンスでは、自己署名証明書は Identity Server のトークン署名のためだけに Azure Key Vault に作成されます。 Identity Server の構成により、アプリの CurrentUser>My 証明書ストアを介して、キー コンテナー証明書が使用されます。 カスタム ドメインで HTTPS トラフィックに使用される他の証明書は、Identity Server の署名証明書とは別に作成および構成されます。

カスタム ドメインと HTTPS を使用してホストするようにアプリ、Azure App Service、Azure Key Vault を構成するには:

  1. プラン レベル Basic B1 以上を使用して、App Service プランを作成します。 App Service でカスタム ドメインを使用するには、Basic B1 以上のサービス レベルが必要です。

  2. 組織で管理するサイトの完全修飾ドメイン名 (FQDN) の共通名を使用して (例: www.contoso.com)、サイトのセキュリティで保護されたブラウザー通信 (HTTPS プロトコル) 用の PFX 証明書を作成します。 次のように証明書を作成します。

    • キーで使用
      • デジタル署名の検証 (digitalSignature)
      • キーの暗号化 (keyEncipherment)
    • 強化および拡張されたキーで使用
      • クライアント認証 (1.3.6.1.5.5.7.3.2)
      • サーバー認証 (1.3.6.1.5.5.7.3.1)

    証明書を作成するには、次のいずれかの方法、または他の適切なツールやオンライン サービスを使用します。

    パスワードを記録しておきます。これは後で Azure Key Vault に証明書をインポートするために使用します。

    Azure Key Vault 証明書の詳細については、Azure Key Vault のCertificate(次へ: 証明書) を選択します。

  3. 新しい Azure キー コンテナーを作成するか、Azure サブスクリプションの既存のキー コンテナーを使用します。

  4. キー コンテナーの [証明書] 領域で、PFX サイト証明書をインポートします。 後でアプリの構成に使用するので、証明書のサムプリントを記録しておきます。

  5. Azure Key Vault で、Identity Server のトークン署名用に新しい自己署名証明書を生成します。 証明書の証明書名サブジェクトを指定します。 サブジェクトCN={COMMON NAME} と指定します。{COMMON NAME} プレースホルダーは証明書の共通名です。 共通名には、任意の英数字を使用できます。 たとえば、CN=IdentityServerSigning は証明書の有効なサブジェクトです。 [発行ポリシー]>[ポリシーの詳細構成] で、既定の設定を使用します。 後でアプリの構成に使用するので、証明書のサムプリントを記録しておきます。

  6. Azure portal で Azure App Service に移動し、次の構成を使用して新しいアプリ サービスを作成します。

    • [発行] は、Code に設定します。
    • [ランタイム スタック] は、アプリのランタイムに設定します。
    • [SKU とサイズ] については、App Service のレベルが Basic B1 以上であることを確認します。 App Service でカスタム ドメインを使用するには、Basic B1 以上のサービス レベルが必要です。
  7. Azure によって App Service が作成された後、アプリの [構成] を開き、前に記録した証明書のサムプリントを指定して新しいアプリケーション設定を追加します。 アプリ設定キーは WEBSITE_LOAD_CERTIFICATES です。 次の例に示すように、アプリの設定値で証明書のサムプリントを区切るにはコンマを使用します。

    • キー: WEBSITE_LOAD_CERTIFICATES
    • 値: 57443A552A46DB...D55E28D412B943565,29F43A772CB6AF...1D04F0C67F85FB0B1

    Azure portal で、アプリの設定の保存は 2 段階のプロセスです。WEBSITE_LOAD_CERTIFICATES のキーと値の設定を保存した後、ブレードの上部にある [保存] ボタンを選択します。

  8. アプリの [TLS/SSL の設定] を選択します。 [秘密キー証明書 (.pfx)] を選択します。 [Key Vault 証明書のインポート] プロセスを使用します。 そのプロセスを "2 回" 使用して、HTTPS 通信用のサイトの証明書と、サイトの自己署名された Identity Server トークン署名証明書の両方をインポートします。

  9. [カスタム ドメイン] ブレードに移動します。 ドメイン レジストラーの Web サイトで、IP アドレスカスタム ドメイン検証 ID を使用して、ドメインを構成します。 一般的なドメイン構成には次のものが含まれます。

    • ホスト@ で、値が Azure portal の IP アドレスである A レコード
    • ホストasuid で、値が Azure によって生成されて Azure portal によって提供される検証 ID である TXT レコード

    ドメイン レジストラーの Web サイトで変更を正しく保存したことを確認します。 レジストラーの Web サイトによっては、ドメイン レコードを保存するために 2 段階のプロセスが必要な場合があります。1 つ以上のレコードを個別に保存した後、別のボタンを使用してドメインの登録を更新します。

  10. Azure portal の [カスタム ドメイン] ブレードに戻ります。 [カスタム ドメインの追加] を選択します。 [A レコード] オプションを選択します。 ドメインを指定し、 [検証] を選択します。 ドメイン レコードが正しく、インターネットを通して反映されている場合、ポータルで [カスタム ドメインの追加] ボタンを選択できます。

    ドメイン登録の変更がインターネットのドメイン ネーム サーバー (DNS) 全体に反映されるまで、ドメイン レジストラーによって処理されてから数日かかることがあります。 ドメイン レコードが 3 営業日以内に更新されない場合は、レコードが正しく設定されていることをドメイン レジストラーで確認し、カスタマー サポートに問い合わせてください。

  11. [カスタム ドメイン] ブレードで、ドメインの [SSL 状態]Not Secure とマークされます。 [バインドの追加] リンクを選択します。 キー コンテナーからカスタム ドメイン バインド用のサイトの HTTPS 証明書を選択します。

  12. Visual Studio で、Server プロジェクトのアプリ設定ファイル (appsettings.json または appsettings.Production.json) を開きます。 Identity Server の構成で、次の Key セクションを追加します。 Name キーに対しては、自己署名証明書のサブジェクトを指定します。 次の例の場合、キー コンテナーで割り当てられている証明書の共通名は IdentityServerSigning であり、それにより CN=IdentityServerSigning というサブジェクトが生成されます。

    "IdentityServer": {
    
      ...
    
      "Key": {
        "Type": "Store",
        "StoreName": "My",
        "StoreLocation": "CurrentUser",
        "Name": "CN=IdentityServerSigning"
      }
    },
    
  13. Visual Studio で、Server プロジェクトに対する Azure App Service の "発行プロファイル" を作成します。 メニュー バーから次のように選択します。 [ビルド]>[発行]>[新規]>[Azure]>[Azure App Service] (Windows または Linux)。 Visual Studio が Azure サブスクリプションに接続されたら、 [リソースの種類] で Azure リソースの [ビュー] を設定できます。 [Web アプリ] の一覧内を移動し、アプリの App Service を見つけて選択します。 [完了] を選択します。

  14. Visual Studio が [発行] ウィンドウに戻ると、キー コンテナーと SQL Server データベース サービスの依存関係が自動的に検出されます。

    Key Vault サービスの既定の設定で構成を変更する必要はありません。

    テストの目的で、既定では Blazor テンプレートによって構成されるアプリのローカル SQLite データベースを、追加の構成を行わずにアプリでデプロイできます。 運用環境で Identity Server 用に別のデータベースを構成する方法については、この記事では説明しません。 詳細については、次のドキュメント セットのデータベース リソースを参照してください。

  15. ウィンドウの上部にある配置プロファイル名の下にある [編集] リンクを選択します。 デプロイ先の URL を、サイトのカスタム ドメイン URL に変更します (例: https://www.contoso.com)。 設定を保存します。

  16. アプリの発行 Visual Studio によってブラウザー ウィンドウが開かれ、カスタム ドメインのサイトが要求されます。

Azure のドキュメントには、App Service の TLS バインドでの Azure サービスとカスタム ドメインの使用に関する詳細が含まれています。これには、A レコードの代わりに CNAME レコードを使用する方法の情報も含まれます。 詳細については、次のリソースを参照してください。

Azure portal でアプリ、アプリ構成、または Azure サービスを変更した後、アプリのテストを実行するたびに、新しいプライベート モード ブラウザー ウィンドウ (Microsoft Edge InPrivate モードや Google Chrome Incognito モードなど) を使用することをお勧めします。 サイトの構成が正しい場合でも、前回のテスト実行から残っている cookie により、サイトをテストするときに認証または承認が失敗する可能性があります。 テストを実行するたびに新しいプライベート ブラウザー ウィンドウを開くように Visual Studio を構成する方法の詳細については、「Cookie とサイト データ」セクションを参照してください。

Azure portal で App Service の構成を変更すると、通常、更新は短時間で有効になりますが、すぐにではありません。 場合によっては、構成の変更を有効にするために App Service が再起動されるまでしばらく待つ必要があります。

Identity Server キー署名証明書の読み込みに関する問題のトラブルシューティングを行う場合は、Azure portal の Kudu PowerShell コマンド シェルで次のコマンドを実行します。 このコマンドにより、アプリで CurrentUser>My 証明書ストアからアクセスできる証明書の一覧が提供されます。 出力には、アプリをデバッグするときに役立つ証明書のサブジェクトとサムプリントが含まれています。

Get-ChildItem -path Cert:\CurrentUser\My -Recurse | Format-List DnsNameList, Subject, Thumbprint, EnhancedKeyUsageList

トラブルシューティング

ログ機能

Blazor WebAssembly 認証のデバッグまたはトレース ログを有効にするには、「ASP.NET Core Blazor のログ」で記事バージョン セレクターを ASP.NET Core 7.0 以降に設定して、「クライアント側認証のログ」セクションを参照してください。

一般的なエラー

  • アプリまたは Identity プロバイダー (IP) の構成の誤り

    最も一般的なエラーの原因は、構成の誤りです。 以下に例を示します。

    • シナリオの要件によっては、権限、インスタンス、テナント ID、テナント ドメイン、クライアント ID、またはリダイレクト URI の欠落または誤りによって、アプリによるクライアントの認証ができなくなります。
    • 要求スコープが正しくないと、クライアントはサーバー Web API エンドポイントにアクセスできません。
    • サーバー API のアクセス許可が正しくないか、存在しないと、クライアントがサーバー Web API エンドポイントにアクセスできなくなります。
    • IP のアプリ登録のリダイレクト URI で構成されているものとは異なるポートでアプリが実行されています。 Microsoft Entra ID と、localhost 開発テスト アドレスで実行されるアプリの場合は、ポートは必要ありませんが、localhost 以外のアドレスの場合は、アプリのポート構成と、アプリが実行されているポートが一致する必要があることに注意してください。

    この記事のガイダンスの構成セクションに、正しい構成の例を示します。 記事の各セクションを慎重に確認して、アプリと IP の構成の誤りを探してください。

    構成が正しい場合:

    • アプリケーション ログを分析します。

    • ブラウザーの開発者ツールを使用して、クライアント アプリと IP またはサーバー アプリの間のネットワーク トラフィックを確認します。 多くの場合、要求を行った後、IP またはサーバー アプリによって、問題の原因を特定する手掛かりを含む正確なエラー メッセージまたはメッセージがクライアントに返されます。 開発者ツールのガイダンスは、次の記事にあります。

    • JSON Web Token (JWT) が使われている Blazor のリリースの場合は、問題が発生している場所に応じて、クライアントの認証またはサーバー Web API へのアクセスに使われるトークンの内容をデコードします。 詳細については、「JSON Web トークン (JWT) の内容を検査する」を参照してください。

    ドキュメント チームは、ドキュメントのフィードバックと記事のバグについては対応します (こちらのページのフィードバック セクションからイシューを作成してください) が、製品サポートを提供することはできません。 アプリのトラブルシューティングに役立つ、いくつかのパブリック サポート フォーラムが用意されています。 次をお勧めします。

    上記のフォーラムは、Microsoft が所有または管理するものではありません。

    セキュリティで保護されておらず、機密でも社外秘でもない再現可能なフレームワークのバグ レポートについては、ASP.NET Core 製品単位でイシューを作成してください。 問題の原因を徹底的に調査し、パブリック サポート フォーラムのコミュニティの助けを借りてもお客様自身で解決できない場合にのみ、製品単位でイシューを作成してください。 単純な構成の誤りやサードパーティのサービスに関連するユース ケースによって破損した個々のアプリのトラブルシューティングは、製品単位で行うことはできません。 レポートが機密性の高い性質のものでる場合や、攻撃者が悪用するおそれのある製品の潜在的なセキュリティ上の欠陥が記述されている場合は、セキュリティの問題とバグの報告 (dotnet/aspnetcore GitHub リポジトリ) を参照してください。

  • ME-ID で承認されないクライアント

    情報:Microsoft.AspNetCore.Authorization.DefaultAuthorizationService[2] 承認に失敗しました。 次の要件が満たされていません。DenyAnonymousAuthorizationRequirement:認証済みユーザーが必要です。

    ME-ID からのログイン コールバック エラー:

    • エラー: unauthorized_client
    • 説明: AADB2C90058: The provided application is not configured to allow public clients.

    このエラーを解決するには:

    1. Azure portal で、アプリのマニフェストにアクセスします。
    2. allowPublicClient 属性null または true に設定します。

Cookie とサイト データ

Cookie とサイト データは、アプリが更新されても保持され、テストやトラブルシューティングに影響する可能性があります。 アプリ コードの変更、プロバイダーによるユーザー アカウントの変更、プロバイダー アプリの構成変更を行うときは、次のものをクリアしてください。

  • ユーザー サインイン cookie
  • アプリ cookie
  • キャッシュおよび保存されたサイト データ

残った cookie とサイト データがテストとトラブルシューティングに影響しないようにする方法を、次に示します。

  • ブラウザーを構成する
    • ブラウザーが閉じるたびに cookie とサイト データをすべて削除するように構成できることをテストするために、ブラウザーを使用します。
    • アプリ、テスト ユーザー、プロバイダー構成が変更されるたびにブラウザーが手動で、または IDE によって閉じられていることを確認します。
  • カスタム コマンドを使用して、Visual Studio でブラウザーを InPrivate または Incognito モードで開きます:
    • Visual Studio の [実行] ボタンをクリックして [ブラウザーの選択] ダイアログボックスを開きます。
    • [追加] ボタンを選びます。
    • [プログラム] フィールドでブラウザーのパスを指定します。 次の実行可能パスが、Windows 10 の一般的なインストール場所です。 ブラウザーが別の場所にインストールされている場合、または Windows 10 を使用していない場合は、ブラウザーの実行可能ファイルのパスを指定してください。
      • Microsoft Edge: C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe
      • Google Chrome: C:\Program Files (x86)\Google\Chrome\Application\chrome.exe
      • Mozilla Firefox: C:\Program Files\Mozilla Firefox\firefox.exe
    • [引数] フィールドに、ブラウザーを InPrivate または Incognito モードで開くために使用するコマンドライン オプションを指定します。 ブラウザーによっては、アプリの URL が必要になる場合があります。
      • Microsoft Edge:-inprivate を使用してください。
      • Google Chrome:--incognito --new-window {URL} を使用します。プレースホルダー {URL} は開く URL (https://localhost:5001 など) です。
      • Mozilla Firefox:-private -url {URL} を使用します。プレースホルダー {URL} は開く URL (https://localhost:5001 など) です。
    • [フレンドリ名] フィールドに名前を指定します。 たとえば、Firefox Auth Testing のようにします。
    • [OK] ボタンを選択します。
    • アプリでテストを繰り返すたびにブラウザー プロファイルを選択する必要がないようにするには、 [既定値として設定] ボタンでプロファイルを既定値として設定します。
    • アプリ、テスト ユーザー、またはプロバイダー構成が変更されるたびに、ブラウザーが IDE によって閉じられていることを確認します。

アプリのアップグレード

開発マシンで .NET Core SDK をアップグレードしたり、アプリ内のパッケージ バージョンを変更したりした直後に、機能しているアプリが失敗することがあります。 場合によっては、パッケージに統一性がないと、メジャー アップグレード実行時にアプリが破壊されることがあります。 これらの問題のほとんどは、次の手順で解決できます。

  1. コマンド シェルから dotnet nuget locals all --clear を実行して、ローカル システムの NuGet パッケージ キャッシュをクリアします。
  2. プロジェクトのフォルダー binobj を削除します。
  3. プロジェクトを復元してリビルドします。
  4. アプリを再展開する前に、サーバー上の展開フォルダー内のすべてのファイルを削除します。

Note

アプリのターゲット フレームワークと互換性のないパッケージ バージョンの使用はサポートされていません。 パッケージの詳細については、NuGet ギャラリーまたは FuGet パッケージ エクスプローラーを使用してください。

Server アプリを実行する

ホステッド Blazor WebAssemblyソリューションのテストとトラブルシューティングを行うときは、Server プロジェクトからアプリを実行していることをご確認ください。

ユーザーを検査する

次の User コンポーネントは、アプリ内で直接使うことも、さらにカスタマイズするための基礎として使うこともできます。

User.razor:

@page "/user"
@attribute [Authorize]
@using System.Text.Json
@using System.Security.Claims
@inject IAccessTokenProvider AuthorizationService

<h1>@AuthenticatedUser?.Identity?.Name</h1>

<h2>Claims</h2>

@foreach (var claim in AuthenticatedUser?.Claims ?? Array.Empty<Claim>())
{
    <p class="claim">@(claim.Type): @claim.Value</p>
}

<h2>Access token</h2>

<p id="access-token">@AccessToken?.Value</p>

<h2>Access token claims</h2>

@foreach (var claim in GetAccessTokenClaims())
{
    <p>@(claim.Key): @claim.Value.ToString()</p>
}

@if (AccessToken != null)
{
    <h2>Access token expires</h2>

    <p>Current time: <span id="current-time">@DateTimeOffset.Now</span></p>
    <p id="access-token-expires">@AccessToken.Expires</p>

    <h2>Access token granted scopes (as reported by the API)</h2>

    @foreach (var scope in AccessToken.GrantedScopes)
    {
        <p>Scope: @scope</p>
    }
}

@code {
    [CascadingParameter]
    private Task<AuthenticationState> AuthenticationState { get; set; }

    public ClaimsPrincipal AuthenticatedUser { get; set; }
    public AccessToken AccessToken { get; set; }

    protected override async Task OnInitializedAsync()
    {
        await base.OnInitializedAsync();
        var state = await AuthenticationState;
        var accessTokenResult = await AuthorizationService.RequestAccessToken();

        if (!accessTokenResult.TryGetToken(out var token))
        {
            throw new InvalidOperationException(
                "Failed to provision the access token.");
        }

        AccessToken = token;

        AuthenticatedUser = state.User;
    }

    protected IDictionary<string, object> GetAccessTokenClaims()
    {
        if (AccessToken == null)
        {
            return new Dictionary<string, object>();
        }

        // header.payload.signature
        var payload = AccessToken.Value.Split(".")[1];
        var base64Payload = payload.Replace('-', '+').Replace('_', '/')
            .PadRight(payload.Length + (4 - payload.Length % 4) % 4, '=');

        return JsonSerializer.Deserialize<IDictionary<string, object>>(
            Convert.FromBase64String(base64Payload));
    }
}

JSON Web トークン (JWT) の内容を検査する

JSON Web トークン (JWT) をデコードするには、Microsoft の jwt.ms ツールを使用します。 UI の値がブラウザーに残ることはありません。

エンコードされた JWT の例 (表示用に短縮されています):

eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6Ilg1ZVhrNHh5b2pORnVtMWtsMll0djhkbE5QNC1j ... bQdHBHGcQQRbW7Wmo6SWYG4V_bU55Ug_PW4pLPr20tTS8Ct7_uwy9DWrzCMzpD-EiwT5IjXwlGX3IXVjHIlX50IVIydBoPQtadvT7saKo1G5Jmutgq41o-dmz6-yBMKV2_nXA25Q

Azure AAD B2C に対して認証するアプリのツールによってデコードされた JWT の例:

{
  "typ": "JWT",
  "alg": "RS256",
  "kid": "X5eXk4xyojNFum1kl2Ytv8dlNP4-c57dO6QGTVBwaNk"
}.{
  "exp": 1610059429,
  "nbf": 1610055829,
  "ver": "1.0",
  "iss": "https://mysiteb2c.b2clogin.com/5cc15ea8-a296-4aa3-97e4-226dcc9ad298/v2.0/",
  "sub": "5ee963fb-24d6-4d72-a1b6-889c6e2c7438",
  "aud": "70bde375-fce3-4b82-984a-b247d823a03f",
  "nonce": "b2641f54-8dc4-42ca-97ea-7f12ff4af871",
  "iat": 1610055829,
  "auth_time": 1610055822,
  "idp": "idp.com",
  "tfp": "B2C_1_signupsignin"
}.[Signature]

その他の技術情報