フォームの自動入力でパスキーを使用してログインする

既存のパスワード ユーザーにも対応しつつ、パスキーを活用するログイン エクスペリエンスを作成します。

このガイドでは、フォームの自動入力を使用して、ユーザーがパスワードとともにパスキーでログインできるようにする方法について説明します。フォームの自動入力を使用すると、統合されたログイン エクスペリエンスが実現し、パスワードからより安全でユーザー フレンドリーなパスキー認証方法への移行が容易になります。

WebAuthn の条件付き UI を実装して、既存のログイン フォームでの手間を最小限に抑えながら、パスキーとパスワードの両方のユーザーをサポートする方法を学びます。

フォームの自動入力でパスキーを使用してログインする理由

パスキーを使用すると、ユーザーは指紋、顔、デバイスの PIN を使用してウェブサイトにログインできます。

すべてのユーザーがパスキーを持っている場合、認証フローは単一のログイン ボタンにすることができます。ボタンをタップすると、画面ロックでアカウントを直接確認してログインできます。

ただし、パスワードからパスキーへの移行には課題があります。この期間中、ウェブサイトはパスワードとパスキーの両方のユーザーをサポートする必要があります。パスキーを使用するサイトをユーザーが覚えておくことを期待し、事前にログイン方法を選択するよう求めると、ユーザー エクスペリエンスが損なわれます。

パスキーも新しいテクノロジーであり、明確に説明するのは難しい場合があります。使い慣れた自動入力インターフェースを使用すると、移行の課題とユーザーの慣れやすさの両方に対応できます。

条件付き UI を使用する

パスキーとパスワードの両方のユーザーを効果的にサポートするには、フォームの自動入力候補にパスキーを含めます。このアプローチでは、 WebAuthn 標準の機能である 条件付き UI を使用します。

フォームの自動入力によるパスキー選択の例。

ユーザーがユーザー名の入力フィールドにフォーカスすると、自動入力ダイアログが表示され、保存されているパスキーと保存済みのパスワードが候補として提示されます。ユーザーはパスキーまたはパスワードを選択してログインに進むことができます。パスキーを選択した場合は、デバイスの画面ロックを使用します。

これにより、ユーザーは既存のログイン フォームを使用してウェブサイトにログインできますが、パスキーの追加のセキュリティ上のメリットも得られます(パスキーがある場合)。

パスキー認証の仕組み

パスキーで認証するには、WebAuthn API を使用します。

パスキー認証フローの 4 つのコンポーネントは次のとおりです。

  • バックエンド: 公開鍵などのユーザー アカウントの詳細を保存します。
  • フロントエンド: ブラウザと通信し、バックエンドから必要なデータを取得します。
  • ブラウザ: JavaScript を実行し、WebAuthn API とやり取りします。
  • パスキー プロバイダ: パスキーを作成して保存します。通常は、Google パスワード マネージャーなどのパスワード マネージャーまたはセキュリティ キーです。
パスキー認証フロー。フロントエンド、バックエンド、ブラウザ、パスキー プロバイダ間のやり取りを示しています。
パスキーの完全な認証フロー。

パスキーの認証プロセスは次のフローになります。

  1. ユーザーがログインページにアクセスすると、フロントエンドバックエンドに認証チャレンジをリクエストします。
  2. バックエンドは、ユーザーのアカウントに関連付けられた WebAuthn チャレンジを生成し、返します。
  3. フロントエンドは、ブラウザを使用して認証を開始するために、チャレンジとともに navigator.credentials.get() を呼び出します。
  4. ブラウザは、パスキー プロバイダとやり取りして、パスキーの選択(多くの場合、ログイン フィールドにフォーカスを当てることでトリガーされる自動入力ダイアログを使用)と、デバイスの画面ロックまたは生体認証による本人確認を求めるメッセージをユーザーに表示します。
  5. ユーザー確認が完了すると、パスキー プロバイダがチャレンジに署名し、ブラウザは生成された公開鍵認証情報(署名を含む)をフロントエンドに返します。
  6. フロントエンドは、この認証情報をバックエンドに送信します。
  7. バックエンドは、認証情報の署名をユーザーの保存されている公開鍵と照合して検証します。検証が成功すると、バックエンドはユーザーをログインさせます。

フォームの自動入力でパスキーを使用して認証する

フォーム自動入力を使用してパスキー認証を開始するには、ログイン ページの読み込み時に条件付きの WebAuthn get 呼び出しを行います。この navigator.credentials.get() の呼び出しには mediation: 'conditional' オプションが含まれています。
WebAuthnnavigator.credentials.get() API への条件付きリクエストでは、UI はすぐに表示されません。代わりに、ユーザーがユーザー名フィールドの自動入力プロンプトを操作するまで保留状態になります。ユーザーがパスキーを選択すると、ブラウザは保留中の Promise を認証情報で解決し、ユーザーをログインさせます。これにより、従来のフォーム送信を回避できます。ユーザーがパスワードを選択した場合、プロミスは解決されず、標準のパスワード ログインフローが続行されます。ユーザーのログインはページの責任となります。

フォームの入力フィールドにアノテーションを付ける

パスキーの自動入力を有効にするには、フォームのユーザー名 input フィールドに autocomplete 属性を追加します。usernamewebauthn の両方の値をスペース区切りで指定します。

<input type="text" name="username" autocomplete="username webauthn" autofocus>

このフィールドに autofocus を追加すると、ページの読み込み時に自動入力プロンプトがトリガーされ、使用可能なパスワードとパスキーがすぐに表示されます。

特徴検出

条件付きの WebAuthn API 呼び出しを呼び出す前に、次の点を確認します。

  • ブラウザが PublicKeyCredential で WebAuthn をサポートしている。

Browser Support

  • Chrome: 67.
  • Edge: 18.
  • Firefox: 60.
  • Safari: 13.

Source

  • ブラウザが PublicKeyCredential.isConditionalMediationAvailable()WebAuthn 条件付き UI をサポートしている。

Browser Support

  • Chrome: 108.
  • Edge: 108.
  • Firefox: 119.
  • Safari: 16.

Source

次のスニペットは、ブラウザがこれらの機能をサポートしているかどうかを確認する方法を示しています。

// Availability of window.PublicKeyCredential means WebAuthn is usable.  
if (window.PublicKeyCredential &&  
    PublicKeyCredential.isConditionalMediationAvailable) {  
  // Check if conditional mediation is available.  
  const isCMA = await PublicKeyCredential.isConditionalMediationAvailable();  
  if (isCMA) {  
    // Call WebAuthn authentication  
  }  
}  

バックエンドから情報を取得する

バックエンドは、navigator.credentials.get() 呼び出しを開始するために、フロントエンドにいくつかのオプションを提供する必要があります。通常、これらのオプションは、サーバーのエンドポイントから JSON オブジェクトとして取得されます。

options オブジェクトの主なプロパティは次のとおりです。

  • challenge: ArrayBuffer 内のサーバー生成チャレンジ(通常は JSON 転送用に Base64URL でエンコードされます)。これはリプレイ攻撃を防ぐために不可欠です。サーバーは、ログイン試行ごとに新しいチャレンジを生成し、試行が失敗した場合や短時間後に無効にする必要があります。
  • allowCredentials: 認証情報記述子の配列。空の配列を渡します。これにより、指定した rpId のすべての認証情報がブラウザに一覧表示されます。
  • userVerification: デバイスの画面ロックの必須化など、ユーザー確認の設定を指定します。デフォルト値と推奨値は "preferred" です。使用できる値は次のとおりです。

    • "required": ユーザー確認は、認証システム(PIN や生体認証など)によって行われる必要があります。確認を実行できない場合、オペレーションは失敗します。
    • "preferred": 認証システムはユーザーの確認を試みますが、確認なしでオペレーションが成功することもあります。
    • "discouraged": 可能であれば、認証システムはユーザー確認を回避する必要があります。
    をご覧ください。
  • rpId: 信頼するパーティ ID(通常はウェブサイトのドメイン(example.com など))。この値は、パスキー認証情報が作成されたときに使用された rp.id と完全に一致する必要があります。

サーバーは、このオプション オブジェクトを作成する必要があります。ArrayBuffer 値(challenge など)は、JSON 転送用に Base64URL でエンコードする必要があります。フロントエンドでは、JSON を解析した後、PublicKeyCredential.parseRequestOptionsFromJSON() を使用してオブジェクト(Base64URL 文字列のデコードを含む)を navigator.credentials.get() が想定する形式に変換します。

次のコード スニペットは、パスキーによる認証に必要な情報を取得してデコードする方法を示しています。

// Fetch an encoded PubicKeyCredentialRequestOptions from the server.
const _options = await fetch('/webauthn/signinRequest');

// Deserialize and decode the PublicKeyCredentialRequestOptions.
const decoded_options = JSON.parse(_options);
const options = PublicKeyCredential.parseRequestOptionsFromJSON(decoded_options);
...

conditional フラグを使用して WebAuthn API を呼び出し、ユーザーを認証する

publicKeyCredentialRequestOptions オブジェクト(以下のサンプルコードでは options という名前)を準備したら、navigator.credentials.get() を呼び出して条件付きパスキー認証を開始します。

// To abort a WebAuthn call, instantiate an AbortController.
const abortController = new AbortController();

// Invoke WebAuthn to authenticate with a passkey.
const credential = await navigator.credentials.get({
  publicKey: options,
  signal: abortController.signal,
  // Specify 'conditional' to activate conditional UI
  mediation: 'conditional'
});

この呼び出しの主なパラメータは次のとおりです。

  • publicKey: サーバーから取得して前の手順で処理した publicKeyCredentialRequestOptions オブジェクト(例では options という名前)にする必要があります。
  • signal: AbortController のシグナル(abortController.signal など)を渡すと、get() リクエストをプログラムでキャンセルできます。これは、別の WebAuthn 呼び出しを呼び出す場合に便利です。
  • mediation: 'conditional': これは、WebAuthn 呼び出しを条件付きにする重要なフラグです。モーダル ダイアログをすぐに表示するのではなく、自動入力プロンプトでユーザー操作を待つようにブラウザに指示します。

返された公開鍵認証情報を RP サーバーに送信する

ユーザーがパスキーを選択し、(デバイスの画面ロックなどを使用して)本人確認に成功すると、navigator.credentials.get() プロミスが解決します。これにより、 PublicKeyCredential オブジェクトがフロントエンドに返されます。

プロミスは、いくつかの理由で拒否されることがあります。これらのエラーは、Error オブジェクトの name プロパティをチェックすることでコード内で処理する必要があります。

  • NotAllowedError: ユーザーがオペレーションをキャンセルしたか、パスキーが選択されていません。
  • AbortError: オペレーションが中止されました。AbortController を使用しているコードが原因の可能性があります。
  • その他の例外: 予期しないエラーが発生しました。通常、ブラウザはユーザーにエラー ダイアログを表示します。

PublicKeyCredential オブジェクトには複数のプロパティが含まれています。認証に関連する主なプロパティは次のとおりです。

  • id: 認証済みパスキー認証情報の base64url でエンコードされた ID。
  • rawId: 認証情報 ID の ArrayBuffer バージョン。
  • response.clientDataJSON: クライアント データの ArrayBuffer。このフィールドには、サーバーで検証する必要があるチャレンジやオリジンなどの情報が含まれます。
  • response.authenticatorData: 認証システム データの ArrayBuffer。このフィールドには、RP ID などの情報が含まれます。
  • response.signature: 署名を含む ArrayBuffer。この値は認証情報の核となる情報であり、サーバーは認証情報の保存されている公開鍵を使用してこの署名を検証する必要があります。
  • response.userHandle: パスキーの登録時に指定したユーザー ID を含む ArrayBuffer。
  • authenticatorAttachment: 認証システムがクライアント デバイスの一部であるか(platform)、外部にあるか(cross-platform)を示します。cross-platform アタッチメントは、 ユーザーがスマートフォンでログインした場合に発生することがあります。そのような場合は、今後のために現在のデバイスでパスキーを作成するようお客様に伝えることを検討してください。
  • type: このフィールドは常に "public-key" に設定されます。

この PublicKeyCredential オブジェクトをバックエンドに送信するには、まず .toJSON() メソッドを呼び出します。このメソッドは、JSON シリアル化可能なバージョンの認証情報を作成します。この認証情報は、ArrayBuffer プロパティ(rawIdclientDataJSONauthenticatorDatasignatureuserHandle など)を Base64URL エンコード文字列に変換します。次に、JSON.stringify() を使用してこのオブジェクトを文字列に変換し、リクエストの本文でサーバーに送信します。

...
// Encode and serialize the PublicKeyCredential.
const _result = credential.toJSON();
const result = JSON.stringify(_result);

// Encode and send the credential to the server for verification.  
const response = await fetch('/webauthn/signinResponse', {
  method: 'post',
  credentials: 'same-origin',
  body: result
});

署名の検証

バックエンド サーバーは、公開鍵認証情報を受信したときに、その真正性を確認する必要があります。これには以下が含まれます。

  1. 認証情報データの解析。
  2. 認証情報の id に関連付けられている保存済みの公開鍵を検索します。
  3. 保存されている公開鍵と受信した signature を照合して検証します。
  4. チャレンジや送信元など、その他のデータの検証。

これらの暗号操作を安全に処理するには、サーバーサイドの FIDO/WebAuthn ライブラリを使用することをおすすめします。オープンソース ライブラリは、awesome-webauthn GitHub リポジトリで入手できます

署名と他のすべてのアサーションが無効でない場合は、サーバーはユーザーをログインできます。サーバーサイドの検証手順について詳しくは、サーバーサイド パスキー認証をご覧ください。

バックエンドで一致する認証情報が見つからない場合にシグナルを送信する

ログイン時にバックエンド サーバーが一致する ID の認証情報を検出できない場合、ユーザーが以前にこのパスキーをサーバーから削除したが、パスキー プロバイダから削除しなかった可能性があります。この不一致により、パスキー プロバイダがサイトで機能しなくなったパスキーを提案し続けると、ユーザー エクスペリエンスが混乱する可能性があります。これを改善するには、孤立したパスキーを削除するようにパスキー プロバイダに通知する必要があります。

Webauthn Signal API の一部である PublicKeyCredential.signalUnknownCredential() メソッドを使用して、指定された認証情報が削除されたか存在しないことをパスキー プロバイダに通知できます。提示された認証情報 ID が不明であることをサーバーが示している場合(404 などの特定の HTTP ステータス コードで示している場合など)、クライアントサイドでこの静的メソッドを呼び出します。このメソッドに RP ID と不明な認証情報 ID を渡します。パスキー プロバイダは、シグナルをサポートしている場合は、パスキーを削除する必要があります。

// Detect authentication failure due to lack of the credential
if (response.status === 404) {
  // Feature detection
  if (PublicKeyCredential.signalUnknownCredential) {
    await PublicKeyCredential.signalUnknownCredential({
      rpId: "example.com",
      credentialId: "vI0qOggiE3OT01ZRWBYz5l4MEgU0c7PmAA" // base64url encoded credential ID
    });
  } else {
    // Encourage the user to delete the passkey from the password manager nevertheless.
    ...
  }
}

認証後

ユーザーがどのようにログインしたかに応じて、おすすめのフローが異なります。

ユーザーがパスキーなしでログインしている場合

ユーザーがパスキーなしでウェブサイトにログインしている場合、そのアカウントまたは現在のデバイスにパスキーが登録されていない可能性があります。これは、パスキーの作成を促す絶好の機会です。次のアプローチを検討してください。

  • パスワードをパスキーにアップグレードする: 条件付き作成を使用します。これは、パスワードでのログインが成功した後に、ブラウザがユーザーのパスキーを自動的に作成できるようにする WebAuthn 機能です。これにより、作成プロセスが簡素化され、パスキーの普及が大幅に促進されます。仕組みと実装方法については、パスキーをよりシームレスにユーザーに導入するをご覧ください。
  • パスキーの作成を手動でプロンプトする: パスキーを作成するようユーザーに促します。これは、ユーザーが多要素認証(MFA)などの複雑なログイン プロセスを完了した後に有効にできます。ただし、過度のメッセージはユーザー エクスペリエンスを妨げる可能性があるため、避けてください。」

パスキーの作成をユーザーに促す方法やその他のおすすめの方法については、パスキーをユーザーに伝えるの例をご覧ください。

ユーザーがパスキーでログインしている場合

ユーザーがパスキーで正常にログインした後、ユーザー エクスペリエンスをさらに向上させ、アカウントの整合性を維持するためのいくつかの方法があります。

クロスデバイス認証後に新しいパスキーを作成することを推奨する

ユーザーがクロスデバイス メカニズム(スマートフォンで QR コードをスキャンするなど)を使用してパスキーでログインした場合、使用したパスキーが、ログインしているデバイスにローカルに保存されないことがあります。これは次のような場合に発生します。

  • パスキーはあるが、ログイン オペレーティング システムまたはブラウザをサポートしていないパスキー プロバイダを使用している。
  • ログインしているデバイスでパスキー プロバイダにアクセスできなくなったが、別のデバイスでパスキーを使用できる。

この場合は、現在のデバイスで新しいパスキーを作成するようユーザーにプロンプトを表示することを検討してください。これにより、今後、クロスデバイスのログイン プロセスを繰り返さずに済みます。ユーザーがクロスデバイス パスキーを使用してログインしたかどうかを確認するには、認証情報の authenticatorAttachment プロパティを確認します。値が "cross-platform" の場合は、クロスデバイス認証を示します。その場合は、新しいパスキーを作成する利便性について説明し、作成手順をご案内します。

シグナルを使用してパスキーの詳細をプロバイダと同期する

一貫性と優れたユーザー エクスペリエンスを確保するために、信頼できる当事者(RP)は WebAuthn Signals API を使用して、認証情報とユーザー情報に関する最新情報をパスキー プロバイダに通知できます。

たとえば、パスキー プロバイダのユーザーのパスキーのリストを正確に保つには、バックエンドの認証情報を同期状態に保ちます。パスキーが存在しないことを通知して、パスキー プロバイダが不要なパスキーを削除できるようにします。

同様に、ユーザーがサービスでユーザー名または表示名を更新した場合にシグナルを送信して、パスキー プロバイダによって表示されるユーザー情報(アカウント選択ダイアログなど)を最新の状態に保つことができます。

パスキーの整合性を維持するためのおすすめの方法については、Signal API を使用して、パスキーとサーバー上の認証情報の整合性を維持するをご覧ください。

2 つ目の要素を要求しない

パスキーは、フィッシングなどの一般的な脅威に対する堅牢な組み込み保護を提供します。そのため、2 つ目の認証要素を追加しても、セキュリティ上の価値は大きくありません。代わりに、ログイン時にユーザーに不要な手順を追加します。

チェックリスト

  • ユーザーがフォームの自動入力でパスキーを使用してログインできるようにする。
  • バックエンドでパスキーの一致する認証情報が見つからない場合にシグナルを送信します。
  • ユーザーがログイン後にパスキーをまだ作成していない場合は、パスキーを手動で作成するようユーザーにプロンプトを表示します。
  • ユーザーがパスワード(および 2 番目の要素)でログインした後にパスキーを自動的に作成する(条件付き作成)。
  • ユーザーがクロスデバイス パスキーでログインしている場合は、ローカル パスキーの作成を求めるプロンプトを表示します。
  • ログイン後または変更が発生したときに、使用可能なパスキーのリストと更新されたユーザーの詳細(ユーザー名、表示名)をプロバイダに通知します。

リソース