次の方法で共有


Web 認証 API (WebAuthn) パスキーを有効にする

パスキーは、 Web 認証 API (WebAuthn)FIDO2 標準に基づく最新のフィッシング耐性認証方法を提供します。 これらは、公開キー暗号化とデバイス ベースの認証を使用して、パスワードに代わるセキュリティで保護された代替手段です。 この記事では、パスキーを使用してユーザーを認証するように ASP.NET Core アプリを構成する方法について説明します。

新規および既存の Blazor Web Appに固有のガイダンスについては、この記事を読んだ後 、ASP.NET Core Blazor Web Appにパスキーを実装する を参照してください。

パスキーとは

パスキーは、暗号化キー ペアを使用するパスワードの代わりです。 秘密キーは、ハードウェア セキュリティ モジュール、プラットフォーム認証システム (例: Windows Hello、Touch ID、Face ID)、パスワード マネージャーなど、ユーザーのデバイスに安全に保存され、公開キーは Web アプリによって格納されます。 認証時に、ユーザーはデバイスを離れることなく秘密キーを所有することを証明します。

パスキーの主な利点は次のとおりです。

  • フィッシング対策: パスキーは特定の Web サイトにバインドされており、偽のサイトでは使用できません。
  • 共有シークレットなし: サーバーは公開キーのみを格納するため、パスワード データベース違反のリスクを排除します。
  • ユーザーの利便性: 単純な生体認証または PIN 検証により、複雑なパスワード要件が置き換えられます。
  • デバイス間同期: 多くのパスキー プロバイダーは、ユーザーのデバイス間で資格情報を同期します。

詳細については、 Web 認証 API (MDN ドキュメント) を参照してください

ASP.NET Core のパスキー Identity

ASP.NET Core Identity には、パスキーの登録と認証のサポートが組み込まれています。

  • Identity インフラストラクチャとのシームレスな統合。
  • 最も一般的な WebAuthn シナリオに対するユーザー認証のサポート。
  • Blazor Web App プロジェクト テンプレートに組み込まれているため、開発者の構成のみが必要です。

Important

ASP.NET Core Identity でのパスキーの実装は、意図的に認証シナリオにスコープが設定されます。 汎用 WebAuthn ライブラリとして意図されていません。 完全な WebAuthn 機能を必要とする開発者は、包括的なプロトコル サポートを提供するコミュニティ ライブラリを検討する必要があります。

サポートされているシナリオ

ASP.NET Core Identity パスキーの実装では、次の主要なシナリオがサポートされています。

  • 既存のアカウントへのパスキーの追加: パスワードベースのアカウントを持つユーザーは、追加の認証方法としてパスキーを登録できます。
  • パスワードレス アカウントの作成: ユーザーは、アカウントの作成時にパスキーを登録することで、パスワードなしでアカウントを作成できます。
  • パスワードなしのサインイン: ユーザーはパスワードを入力せずに、パスキーのみを使用して認証できます。

制限事項

現在の実装には、次の制限があります。

  • ASP.NET Core Identityにスコープが設定されている: API は、Identity認証シナリオ専用に設計されています。
  • 既定の構成証明検証なし: この実装では、既定では構成証明ステートメントは検証されません。
  • テンプレートのサポート: パスキーのサポートが含まれるのは、 Blazor Web App テンプレートのみです。
  • 組み込みの 2FA サポートなし: パスキーは、第 2 要素としてではなく、プライマリ認証要素として扱われます。

主要な概念

パスキー操作を支える 2 つの基本的なプロセス:構成証明とアサーション。

構成証明 (登録)

構成証明 は、新しいパスキーを作成して登録するプロセスです。 構成証明中に、サーバーは、返された資格情報に認証子が含める必要がある一意のチャレンジを生成します。 認証子は新しいキー ペアを作成し、キーの生成元を証明する構成証明データと共に公開キーを返します。 その後、サーバーはこの構成証明を検証し、今後の認証試行のために公開キーを格納します。

アサーション (認証)

アサーション は、既存のパスキーを使用して認証するプロセスです。 サーバーは一意のチャレンジを生成します。このチャレンジは、認証子が秘密キーを使用して署名します。 認証子は、この署名付きアサーションをサーバーに返します。これにより、以前に格納された公開キーを使用して署名が検証されます。 署名が有効な場合、ユーザーは認証されます。

[前提条件]

  • .NET SDK (.NET 10 以降)
  • WebAuthn をサポートする最新の Web ブラウザー。
  • Windows Hello や Apple のセキュリティで保護されたエンクレーブ、またはセキュリティ キーなどのプラットフォーム認証子を持つデバイス。

セキュリティに関する考慮事項

ASP.NET Core Identity でパスキーを実装する場合は、アプリがこのセクションで説明するセキュリティ要件を満たしていることを確認します。

ホスト ヘッダーの検証

この実装は、 ServerDomain が明示的に構成されていない場合に、ホスト ヘッダーから証明書利用者 ID を推論します。 ホスティング環境では、資格情報スコープ攻撃を防ぐためにホスト ヘッダーを検証する必要があります。これには、侵害されたユーザー資格情報または盗まれたユーザー資格情報 (ユーザー名、パスワード、トークン) を使用して承認されていないアクセスを取得する必要があります。

軽減策: ServerDomainIdentityPasskeyOptionsを明示的に構成するか、ホスト環境 (Kestrel、IIS、リバース プロキシ) がホスト ヘッダーを検証することを確認します。 構成の詳細については、ホスティング プラットフォームのドキュメントを参照してください。

サブドメインのセキュリティ

ASP.NET Core のパスキーの実装では、 ServerDomain 構成オプションを使用してサブドメインのセキュリティが処理されます。 ServerDomainが明示的に指定されていない場合、実装はホスト ヘッダーを使用してドメインを決定します。 つまり 、パスキーが登録されたページ によって、その資格情報のドメインが制御されます。

例えば次が挙げられます。

  • パスキーが app.contoso.comに登録されている場合は、 *.app.contoso.comでも機能します。
  • contoso.comに登録されている場合は、*.contoso.comでも機能します。
  • ブラウザーは、パスキーが登録されたドメイン (およびサブドメイン) でのみ使用できるように強制します。

要件: 厳密なドメイン制御を必要とするアプリは、ホスト ヘッダーに依存するのではなく、 ServerDomain を明示的に設定する必要があります。 ServerDomainスコープ内のサブドメインで信頼されていないコンテンツを提供しないでください。 これを保証できない場合は、 カスタムオリジン検証 を実装して、パスキーの使用を特定のオリジンに制限します。

HTTPS 要件

すべてのパスキー操作には HTTPS が必要です。 この実装では、暗号化されていない接続を介して傍受される可能性がある、暗号化された署名された Cookie に認証データが格納されます。

要件: 運用環境では常に HTTPS を使用します。 プロトコル ダウングレード攻撃を防ぐために HTTP Strict Transport Security Protocol (HSTS) を構成します。

アカウントの回復

アカウントの回復は、主に、唯一の認証メカニズムとしてパスキーを許可するアプリの問題です。 既定の Blazor Web App プロジェクト テンプレートでは、アカウントの作成時にバックアップ認証方法 (パスワードまたは外部プロバイダー) を設定する必要があるため、アカウントの回復は、これらの既存のメカニズムを使用して処理されます。

推奨事項:

パスキーのみの認証を実装するアプリケーションでは、次の点を考慮してください。

  • アカウントの作成時に生成された回復コード。
  • 電子メール ベースの回復フロー。
  • 複数のパスキーの必須登録。
  • IsBackedUpUserPasskeyInfo フラグを監視して、ユーザーに資格情報の追加を求めます。

管理コントロール

セキュリティの脆弱性が検出された認証モデルでは、影響を受ける資格情報の取り消しが必要になる場合があります。 実装では、認証子構成証明 GUID (AAGUID) など、各資格情報を持つ完全な構成証明オブジェクトが格納されます。これは、キーの種類を示す 128 ビット識別子です。

実装: 格納されている構成証明オブジェクトから AAGUID を抽出し、侵害された既知のモデルと比較し、影響を受ける資格情報を取り消します。 AAGUID の信頼性は、アプリが構成証明ステートメントを検証するかどうかによって異なります。 カスタム構成証明ステートメントの検証ロジックをフックするには、「 カスタム構成証明ステートメントの検証」を参照してください。 Passkeys - FIDO2 .NET Library (WebAuthn) (passwordless-lib/fido2-net-lib GitHub リポジトリ)†など、サード パーティ製ライブラリを構成証明検証に使用できます。

Warnung

passwordless-lib/fido2-net-libを含むサードパーティ製ライブラリは、Microsoft によって所有または管理されておらず、Microsoft サポート契約またはライセンスの対象ではありません。 サード パーティ製ライブラリを採用する場合は、特にセキュリティ機能に注意してください。 ライブラリが公式の仕様に従い、セキュリティのベスト プラクティスを採用していることを確認します。 最新のバグ修正を取得するには、ライブラリのバージョンを最新の状態に保ちます。

リソースの制限

データベース枯渇攻撃を防ぐために、アプリではパスキーの登録に次のような制限を適用する必要があります。

  • ユーザー アカウントあたりのパスキーの最大数。
  • パスキー表示名の最大長。

Blazor Web App テンプレートでは、既定でアプリケーション レベルでこれらの制限が適用されます。 例については、Razor プロジェクト テンプレートの次のBlazor Web App コンポーネントを参照してください。

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

パスキー オプションを構成する

ASP.NET Core Identity には、 IdentityPasskeyOptions クラスを介してパスキーの動作を構成するためのさまざまなオプションが用意されています。これには、次のものが含まれます。

  • AuthenticatorTimeout: 認証子がパスキーを TimeSpanとして提供するまでブラウザーが待機する時間を取得または設定します。 このオプションは、新しいパスキーの作成と既存のパスキーの要求の両方に適用されます。 このオプションはブラウザーのヒントとして扱われ、ブラウザーはオプションを無視できます。 既定値は 5 分です。
  • ChallengeSize: 構成証明とアサーション中にクライアントに送信されるチャレンジのサイズ (バイト単位) を取得または設定します。 このオプションは、新しいパスキーの作成と既存のパスキーの要求の両方に適用されます。 既定値は 32 バイトです。
  • ServerDomain: サーバーの有効な証明書利用者 ID (ドメイン) を取得または設定します。 これは一意である必要があり、サーバーの ID として使用されます。 このオプションは、新しいパスキーの作成と既存のパスキーの要求の両方に適用されます。 既定値 null場合は、サーバーの配信元が使用されます。 詳細については、「 証明書利用者識別子 RP ID」を参照してください。

構成例:

builder.Services.Configure<IdentityPasskeyOptions>(options =>
{
    options.ServerDomain = "contoso.com";
    options.AuthenticatorTimeout = TimeSpan.FromMinutes(3);
    options.ChallengeSize = 64;
});

構成オプションの完全な一覧については、 IdentityPasskeyOptionsを参照してください。 最新のブラウザーの既定値については、W3C WebAuthn の仕様を参照してください。

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

カスタム構成証明ステートメントの検証

既定では、ASP.NET Core Identity は構成証明ステートメントを検証しません。 これは、ほとんどのコンシューマー認証シナリオに適しています。 アプリで認証子のプロパティの検証が必要な場合、または特定の認証子の使用を禁止する必要がある場合 (たとえば、より高いレベルのセキュリティを必要とするエンタープライズ環境で)、カスタム構成証明検証を実装できます。

builder.Services.Configure<IdentityPasskeyOptions>(options =>
{
    options.VerifyAttestationStatement = async (context) =>
    {
        // Custom attestation validation logic
        // Return 'true' if the attestation is valid
        // Return 'false' if the attestation is invalid
        return true;
    };
});

Warnung

構成証明の検証は複雑であり、認証子証明書の信頼ストアを維持する必要があります。 アプリで特定の認証子プロパティの検証が必要な場合にのみ、カスタム検証を実装します。

カスタム配信元の検証

既定の配信元検証では、サブドメインからの要求が許可され、クロスオリジン iframe は許可されません。 この動作をカスタマイズするには:

builder.Services.Configure<IdentityPasskeyOptions>(options =>
{
    options.ValidateOrigin = async (context) =>
    {
        // Custom origin validation logic
        //   Access the origin via 'context.Origin'
        //   Access the HTTP context via 'context.HttpContext'
        // Return 'true' if the origin is valid
        // Return 'false' if the origin is invalid
        return true;
    };
});

登録フロー

このセクションでは、パスキー登録プロセスの各手順について説明し、ASP.NET Core Identity がパスキー資格情報の作成と保存を容易にする方法について説明します。

sequenceDiagram
    participant Authenticator
    participant User
    participant Browser
    participant Server

    User->>Browser: Click "Add passkey"
    Browser->>Server: Request creation options
    Server->>Browser: Return creation options
    Browser->>Authenticator: Request new credential
    Authenticator->>User: Verify identity (biometric/PIN)
    User->>Authenticator: Approve
    Authenticator->>Browser: Return credential
    Browser->>Server: Submit credential
    Server->>Server: Verify and store
    Server->>Browser: Registration complete
    Browser->>User: Success message

手順 1: 登録の開始

登録プロセスは、ユーザーが自分のアカウントにパスキーを追加することを決定したときに開始されます。 これは通常、アプリのユーザー インターフェイスのボタンまたはリンクを介して行われます。 選択すると、この要素によって JavaScript コードがトリガーされ、登録フローが調整されます。

クライアント側の実装は、アプリによって大きく異なります。 Blazor Web App テンプレートでは、PasskeySubmit.razor.jsの完全な例を見つけることができます。これは、カスタム Web コンポーネントが登録の開始を処理し、後続の WebAuthn API 呼び出しを管理する方法を示しています。

手順 2: 作成オプションを要求する

登録が開始された後、ブラウザーはサーバーから作成オプションを取得する必要があります。 これらのオプションは、作成する資格情報の種類をブラウザーに通知し、署名する必要があるチャレンジなどの重要なセキュリティ パラメーターを含めます。

ブラウザーの観点から見ると、この手順では、サーバーへの HTTP 要求を行う必要があります。

async function createCredential(headers, signal) {
  // Step 2: Request creation options from the server
  const optionsResponse = 
    await fetchWithErrorHandling('/Account/PasskeyCreationOptions', 
    {
      method: 'POST',
      headers,
      signal,
    });
  const optionsJson = await optionsResponse.json();
  const options = PublicKeyCredential.parseCreationOptionsFromJSON(optionsJson);
  return await navigator.credentials.create({ publicKey: options, signal });
}

アプリケーションでは、次のオプションを生成するエンドポイントを定義する必要があります。

app.MapPost("/Account/PasskeyCreationOptions", async (
    HttpContext context,
    UserManager<ApplicationUser> userManager,
    SignInManager<ApplicationUser> signInManager) =>
{
    var user = await userManager.GetUserAsync(context.User);

    if (user is null)
    {
        return Results.NotFound();
    }

    var userId = await userManager.GetUserIdAsync(user);
    var userName = await userManager.GetUserNameAsync(user) ?? "User";
    
    var optionsJson = await signInManager.MakePasskeyCreationOptionsAsync(new()
    {
        Id = userId,
        Name = userName,
        DisplayName = userName
    });
    
    return TypedResults.Content(optionsJson, contentType: "application/json");
});

MakePasskeyCreationOptionsAsyncメソッドは、このプロセスの中心です。 このメソッドは、パスキーを作成するユーザーを記述する PasskeyUserEntity を受け入れます。 このエンティティには、ユーザーの ID、ユーザー名 (通常はメール アドレス)、人間が判読できる表示名が含まれます。 このメソッドは、次の手順でブラウザーが使用する WebAuthn PublicKeyCredentialCreationOptions スキーマに準拠する JSON 文字列を返します。 また、このメソッドはバックグラウンドで認証 cookie に一時的な状態を格納して、ブラウザーからの応答がこれらの特定のオプションに対応していることを確認します。

手順 3: サーバーがオプションを生成する

MakePasskeyCreationOptionsAsync実行すると、アプリのIdentityPasskeyOptions構成を使用して、資格情報を作成するための特定のパラメーターが決定されます。 これらのオプションは、パスキー作成プロセスのさまざまな側面を制御します。

これらのオプションは、アプリケーションの起動時にカスタマイズできます。 例えば次が挙げられます。

builder.Services.Configure<IdentityPasskeyOptions>(options =>
{
    options.ServerDomain = "contoso.com";
    options.AuthenticatorTimeout = TimeSpan.FromMinutes(3);
    options.UserVerificationRequirement = "required";
    options.ResidentKeyRequirement = "preferred";
});

UserVerificationRequirement オプションは、認証子がユーザーの ID を (生体認証または PIN メソッドを使用して) 検証する必要があるかどうかを決定します。一方、ResidentKeyRequirementは資格情報を検出可能にする必要があるかどうかを示し、最初にユーザー名を指定せずに認証を許可します。 詳細については、IdentityPasskeyOptionsを参照してください。

手順 4: クライアントが資格情報を要求する

作成オプションが使用可能な状態で、クライアント側の JavaScript はオプションを WebAuthn API に渡して新しい資格情報を作成します。

async function createCredential(headers, signal) {
  // Step 4: Parse the options and request a new credential from the authenticator
  const optionsResponse = 
    await fetchWithErrorHandling('/Account/PasskeyCreationOptions', 
    {
      method: 'POST',
      headers,
      signal,
    });
  const optionsJson = await optionsResponse.json();
  const options = PublicKeyCredential.parseCreationOptionsFromJSON(optionsJson);
  return await navigator.credentials.create({ publicKey: options, signal });
}

parseCreationOptionsFromJSON関数は、JSON 応答を WebAuthn API で想定される形式に変換し、navigator.credentials.create()認証子を使用して資格情報の作成プロセスを開始します。

手順 5: Authenticator の対話

この時点で、ブラウザーは認証子と通信して資格情報を作成します。 認証子は、指紋のスキャン、PIN の入力、顔認識の使用を伴う可能性がある検証をユーザーに求めます。 この操作は、ブラウザーと認証子によって完全に処理され、アプリ コードは必要ありません。 ユーザー エクスペリエンスは、認証システムの種類とプラットフォームの機能によって異なります。

手順 6: 資格情報の送信

認証子が資格情報を作成した後、ブラウザーは検証と保存のために資格情報をサーバーに送り返す必要があります。 送信する前に、資格情報を JSON にシリアル化する必要があります。

async function createCredential(headers, signal) {
  // Step 6: The credential is returned from navigator.credentials.create()
  // and is serialized to JSON for submission to the server
  const optionsResponse = 
    await fetchWithErrorHandling('/Account/PasskeyCreationOptions', 
    {
      method: 'POST',
      headers,
      signal,
    });
  const optionsJson = await optionsResponse.json();
  const options = PublicKeyCredential.parseCreationOptionsFromJSON(optionsJson);
  return await navigator.credentials.create({ publicKey: options, signal });
}

Blazor Web App テンプレートでは、返された資格情報はフォームを介して自動的にシリアル化および送信されますが、正確な送信メカニズムはアプリケーションによって異なります。

手順 7: サーバーの検証とストレージ

サーバーは、資格情報を受け取ったら、その有効性を確認し、将来の認証のために公開キーを格納する必要があります。 ここで、コア Identityのパスキー API ASP.NET が重要になります。

PerformPasskeyAttestationAsync メソッドは、クライアントからの構成証明応答を検証します。 この包括的な検証プロセス:

  • 資格情報の種類が期待値と一致することを確認します。
  • 配信元とチャレンジを含むクライアント データ JSON を検証します。
  • ユーザーのプレゼンスと検証の認証システム データ フラグを確認します
  • 公開キーを抽出して検証します。

すべてのチェックに合格すると、検証済みのパスキー情報を含む PasskeyAttestationResult が返されます。

構成証明が検証されると、アプリは AddOrUpdatePasskeyAsync を使用してパスキーをデータベースに格納します。

var attestationResult = 
    await signInManager.PerformPasskeyAttestationAsync(credentialJson);

if (!attestationResult.Succeeded)
{
    return Results.BadRequest($"Error: {attestationResult.Failure.Message}");
}

var addResult = 
    await userManager.AddOrUpdatePasskeyAsync(user, attestationResult.Passkey);

if (!addResult.Succeeded)
{
    return Results.BadRequest("Failed to store passkey");
}

格納された UserPasskeyInfo には、資格情報 ID、公開キー、再生保護用の署名カウンター、パスキーがバックアップされているか、バックアップの対象であるかを示すフラグなど、今後の認証に必要なすべての情報が含まれます。

手順 8: 登録後のタスク

パスキーを正常に登録すると、アプリは多くの場合、ユーザー エクスペリエンスを向上させるために追加のタスクを実行します。 一般的なパターンは、ユーザーにパスキーのフレンドリ名を指定するように求め、複数の資格情報の間で識別しやすくすることです。 UserPasskeyInfo.Name プロパティには、このわかりやすい名前が格納されます。この名前は、同じ AddOrUpdatePasskeyAsync メソッドを使用して更新できます。

passkey.Name = "My iPhone";
await userManager.AddOrUpdatePasskeyAsync(user, passkey);

認証フロー

このセクションでは、サインイン プロセスの開始から認証されたセッションの確立まで、ユーザーがパスキーを使用して認証する方法について説明します。

sequenceDiagram
    participant Authenticator
    participant User
    participant Browser
    participant Server

    User->>Browser: Click "Sign in with passkey"
    Browser->>Server: Request authentication options
    Server->>Browser: Return authentication options
    Browser->>Authenticator: Request assertion
    Authenticator->>User: Verify identity
    User->>Authenticator: Approve
    Authenticator->>Browser: Return signed assertion
    Browser->>Server: Submit assertion
    Server->>Server: Verify signature
    Server->>Browser: Authentication complete
    Browser->>User: Redirect to app

手順 1: 認証を開始する

通常、ユーザーはログイン ページの専用ボタンまたはリンクを使用してパスキー認証を開始します。 一部のアプリでは条件付き UI もサポートされています。この UI では、ユーザー名フィールドにオートフィル候補としてパスキーが表示されます。 開始メソッドは、登録プロセスと同様に、認証フローを管理する JavaScript コードをトリガーします。

手順 2: 認証オプションを要求する

ブラウザーは、認証プロセスを開始するためにサーバーに認証オプションを要求します。 これらのオプションには、受け入れ可能な資格情報の一覧と、署名する新しいチャレンジが含まれます。

async function requestCredential(email, mediation, headers, signal) {
  // Step 2: Request authentication options from the server
  const optionsResponse = 
    await fetchWithErrorHandling(`/Account/PasskeyRequestOptions?username=${email}`, 
    {
      method: 'POST',
      headers,
      signal,
    });
  const optionsJson = await optionsResponse.json();
  const options = PublicKeyCredential.parseRequestOptionsFromJSON(optionsJson);
  return await navigator.credentials.get({ publicKey: options, mediation, signal });
}

MakePasskeyRequestOptionsAsyncメソッドは、これらのオプションを生成します。 特定のユーザーを指定すると、そのユーザーの資格情報のみが許可リストに含まれます。 ユーザーなしで呼び出されると、条件付き UI またはユーザー名なしの認証に適したオプションが生成されます。

app.MapPost("/Account/PasskeyRequestOptions", async (
    SignInManager<ApplicationUser> signInManager,
    string? username) =>
{
    var user = string.IsNullOrEmpty(username) 
        ? null 
        : await userManager.FindByNameAsync(username);

    var optionsJson = await signInManager.MakePasskeyRequestOptionsAsync(user);

    return TypedResults.Content(optionsJson, contentType: "application/json");
});

手順 3: サーバーがオプションを生成する

サーバーは、登録時に使用されるのと同じ IdentityPasskeyOptions 構成を使用して認証オプションを生成します。 ServerDomainは、パスキーが最初に登録されたドメインと一致している必要があります。または、認証が失敗します。 UserVerificationRequirementは、認証時に認証システムがユーザーの ID を検証する必要があるかどうかを決定します。

手順 4: クライアントがアサーションを要求する

クライアント側の JavaScript は、認証オプションを WebAuthn API に渡して、認証子にアサーションを要求します。

async function requestCredential(email, mediation, headers, signal) {
  // Step 4: Parse the options and request an assertion from the authenticator
  const optionsResponse = 
    await fetchWithErrorHandling(`/Account/PasskeyRequestOptions?username=${email}`, 
    {
      method: 'POST',
      headers,
      signal,
    });
  const optionsJson = await optionsResponse.json();
  const options = PublicKeyCredential.parseRequestOptionsFromJSON(optionsJson);
  return await navigator.credentials.get({ publicKey: options, mediation, signal });
}

navigator.credentials.get()呼び出しは、認証システムを使用して認証プロセスを開始し、ユーザーに検証を求めます。

手順 5: Authenticator の検証

認証子はユーザーの ID を検証し、秘密キーを使用してチャレンジに署名します。 このプロセスは、登録時の検証手順と同様に、ブラウザーと認証子によって完全に処理されます。 ユーザー エクスペリエンスは認証子の種類によって異なり、生体認証または PIN エントリが含まれる場合があります。

手順 6: アサーションの送信

認証子が署名付きアサーションを作成した後、ブラウザーはそれを JSON にシリアル化し、サーバーに送信します。

async function requestCredential(email, mediation, headers, signal) {
  // Step 6: The assertion is returned from navigator.credentials.get()
  // and is serialized to JSON for submission to the server
  const optionsResponse = 
    await fetchWithErrorHandling(`/Account/PasskeyRequestOptions?username=${email}`, 
    {
      method: 'POST',
      headers,
      signal,
    });
  const optionsJson = await optionsResponse.json();
  const options = PublicKeyCredential.parseRequestOptionsFromJSON(optionsJson);
  return await navigator.credentials.get({ publicKey: options, mediation, signal });
}

送信メカニズムはアプリによって異なりますが、通常はフォームの送信または API 呼び出しが含まれます。

手順 7: サーバーの検証

サーバーは、ユーザーを認証するためのアサーションを検証します。 ASP.NET Core Identity は、1 回の呼び出しで完全な認証フローを実行する PasskeySignInAsync メソッドを提供します。

var result = await signInManager.PasskeySignInAsync(credentialJson);

if (result.Succeeded)
{
    return Results.Ok("Authentication successful");
}

return Results.Unauthorized();

PasskeySignInAsync メソッドは、次のPerformPasskeyAssertionAsyncを内部的に呼び出します。

  • 格納されている公開キーを使用してアサーション署名を検証します。
  • チャレンジが最初に送信されたものと一致することを確認します。
  • ユーザーのプレゼンスと検証の認証フラグを確認します。
  • リプレイ攻撃を防ぐために署名カウンターを更新します。

すべてのチェックに合格すると、メソッドはユーザーをサインインさせ、成功を示す SignInResult を返します。

より詳細な制御が必要なシナリオでは、 PerformPasskeyAssertionAsync を直接使用して、ユーザーをすぐにサインインさせずにアサーションを検証できます。

手順 8: セッションの確立

認証が成功すると、ASP.NET Core Identity はユーザーの認証済みセッションを確立します。 PasskeySignInAsyncメソッドはこれを自動的に処理し、必要な認証 Cookie と要求を作成します。 その後、アプリはユーザーを保護されたリソースにリダイレクトするか、個人用に設定されたコンテンツを表示します。

PublicKeyCredential.toJSON エラーを軽減する (TypeError: Illegal invocation)

PublicKeyCredential.toJSON メソッドは、PublicKeyCredentialの JSON 表現を返します。 このメソッドは、アプリがユーザーの登録または認証中にPublicKeyCredentialを呼び出してJSON.stringifyをシリアル化しようとしたときに、パスワード マネージャーによって呼び出されます。

一部のパスワード マネージャーでは、 PublicKeyCredential.toJSON メソッドが正しく実装されていません。これは、パスキー資格情報をシリアル化するときに JSON.stringify が機能するために必要です。 Blazor Web App プロジェクト テンプレートに基づいてユーザーをアプリに登録または認証すると、パスキーを追加しようとすると、一部のパスワード マネージャーによって次のエラーがスローされます。

Error: Could not add a passkey: Illegal invocation

選択したパスワード マネージャーが更新され、 PublicKeyCredential.toJSON メソッドが正しく実装されるまで、アプリに次の変更を加えます。 次のコードは、手動で JSON によって PublicKeyCredentialをシリアル化します。

Components/Account/Shared/PasskeySubmit.razor.js ファイルで、passkey-submitカスタム要素定義コード ブロックを見つけます。

customElements.define('passkey-submit', class extends HTMLElement {
  ...
});

次の convertToBase64 関数をコード ブロックに追加します。

convertToBase64(o) {
  if (!o) {
    return undefined;
  }

  // Normalize Array to Uint8Array
  if (Array.isArray(o)) {
    o = Uint8Array.from(o);
  }

  // Normalize ArrayBuffer to Uint8Array
  if (o instanceof ArrayBuffer) {
    o = new Uint8Array(o);
  }

  // Convert Uint8Array to base64
  if (o instanceof Uint8Array) {
    let str = '';
    for (let i = 0; i < o.byteLength; i++) {
      str += String.fromCharCode(o[i]);
    }
    o = window.btoa(str);
  }

  if (typeof o !== 'string') {
    throw new Error("Could not convert to base64 string");
  }

  // Convert base64 to base64url
  o = o.replace(/\+/g, "-").replace(/\//g, "_").replace(/=*$/g, "");

  return o;
}

コード ブロックの obtainAndSubmitCredential 関数で、ユーザーの資格情報で JSON.stringify を呼び出す行を見つけて、行を削除します。

- const credentialJson = JSON.stringify(credential);

前の行を次のコードに置き換えます。

const credentialJson = JSON.stringify({
  authenticatorAttachment: credential.authenticatorAttachment,
  clientExtensionResults: credential.getClientExtensionResults(),
  id: credential.id,
  rawId: this.convertToBase64(credential.rawId),
  response: {
    attestationObject: this.convertToBase64(credential.response.attestationObject),
    authenticatorData: this.convertToBase64(credential.response.authenticatorData ?? 
      credential.response.getAuthenticatorData?.() ?? undefined),
    clientDataJSON: this.convertToBase64(credential.response.clientDataJSON),
    publicKey: this.convertToBase64(credential.response.getPublicKey?.() ?? undefined),
    publicKeyAlgorithm: credential.response.getPublicKeyAlgorithm?.() ?? undefined,
    transports: credential.response.getTransports?.() ?? undefined,
    signature: this.convertToBase64(credential.response.signature),
    userHandle: this.convertToBase64(credential.response.userHandle),
  },
  type: credential.type,
});

上記の回避策は、 PublicKeyCredential.toJSON メソッドを正しく実装するためにパスワード マネージャーが更新されるまで必要です。 パスワード マネージャーのリリース ノートを追跡し、パスワード マネージャーの更新後に上記の変更を元に戻することをお勧めします。

その他のリソース