既存のパスワード ユーザーにも対応しつつ、パスキーを活用したログイン プロセスを作成します。
このガイドでは、フォームの自動入力を使用して、パスワードとともにパスキーでユーザーがログインできるようにする方法について説明します。フォームの自動入力を使用すると、ログイン エクスペリエンスが統一され、パスワードからより安全でユーザー フレンドリーなパスキー認証方法への移行が簡素化されます。
WebAuthn の条件付き UI を実装して、既存のログイン フォームでパスキー ユーザーとパスワード ユーザーの両方を最小限の摩擦でサポートする方法を学びます。
パスキーでログインする際にフォームの自動入力を使用する理由
パスキーを使用すると、ユーザーは指紋、顔認証、デバイスの PIN を使用してウェブサイトにログインできます。
すべてのユーザーがパスキーを使用している場合、認証フローは 1 つのログインボタンになります。ボタンをタップすると、画面ロックでアカウントを直接確認してログインできます。
ただし、パスワードからパスキーへの移行には課題があります。この期間中、ウェブサイトはパスワード ユーザーとパスキー ユーザーの両方をサポートする必要があります。ユーザーにパスキーを使用するサイトを覚えてもらい、ログイン方法を事前に選択してもらうと、ユーザー エクスペリエンスが低下します。
パスキーは新しいテクノロジーであり、明確に説明することは難しい場合があります。使い慣れた自動入力インターフェースを使用することで、移行の課題とユーザーの習熟度の両方に対応できます。
条件付き UI を使用する
パスキーとパスワードの両方のユーザーを効果的にサポートするには、フォームの自動入力候補にパスキーを含めます。このアプローチでは、 WebAuthn 標準の機能である 条件付き UI を使用します。
ユーザーがユーザー名の入力フィールドにフォーカスすると、自動入力ダイアログが表示され、保存済みのパスワードとともに保存済みのパスキーが候補として提示されます。ユーザーはパスキーまたはパスワードを選択してログインできます。パスキーを選択した場合は、デバイスの画面ロックを使用します。
これにより、ユーザーは既存のログイン フォームを使用してウェブサイトにログインできます。また、パスキーを登録している場合は、パスキーによるセキュリティ強化のメリットも得られます。
パスキー認証の仕組み
パスキーで認証するには、WebAuthn API を使用します。
パスキー認証フローの 4 つのコンポーネントは次のとおりです。
- バックエンド: 公開鍵を含むユーザー アカウントの詳細を保存します。
- フロントエンド: ブラウザと通信し、バックエンドから必要なデータを取得します。
- ブラウザ: JavaScript を実行し、WebAuthn API とやり取りします。
- パスキー プロバイダ: パスキーを作成して保存します。通常は、Google パスワード マネージャーなどのパスワード マネージャー、またはセキュリティ キーです。
パスキー認証プロセスは次のフローに従います。
- ユーザーがログインページにアクセスすると、フロントエンドが バックエンドから認証チャレンジをリクエストします。
- バックエンドが、ユーザーのアカウントに関連付けられた WebAuthn チャレンジを生成して返します。
- フロントエンドがチャレンジを使用して
navigator.credentials.get()を呼び出し、ブラウザを使用した認証を開始します。 - ブラウザは、パスキー プロバイダとやり取りして、ユーザーにパスキーの選択を促し(多くの場合、ログイン フィールドにフォーカスすることでトリガーされる自動入力ダイアログを使用)、デバイスの画面ロックまたは生体認証を使用して本人確認を行います。
- ユーザーの確認が完了すると、パスキー プロバイダがチャレンジに署名し、ブラウザが結果の公開鍵認証情報(署名を含む)をフロントエンドに返します。
- フロントエンドは、この認証情報をバックエンドに送信します。
- バックエンドは、ユーザーの保存された公開鍵に対して認証情報の署名を検証します。検証に成功すると、バックエンドはユーザーをログインさせます。
フォームの自動入力でパスキーを使用して認証する
フォームの自動入力を使用してパスキー認証を開始するには、ログインページが読み込まれたときに条件付きの WebAuthn get 呼び出しを行います。この navigator.credentials.get() の呼び出しには mediation: 'conditional' オプションが含まれています。
WebAuthn の navigator.credentials.get() API への条件付きリクエストでは、UI がすぐに表示されません。代わりに、ユーザーがユーザー名フィールドの自動入力プロンプトを操作するまで、保留状態で待機します。ユーザーがパスキーを選択すると、ブラウザはユーザーをログインさせる認証情報で保留中の Promise を解決し、従来のフォーム送信をバイパスします。ユーザーがパスワードを選択した場合、Promise は解決されず、標準のパスワード ログインフローが続行されます。ユーザーのログインはページの責任となります。
フォーム入力フィールドにアノテーションを付ける
パスキーの自動入力機能を有効にするには、フォームのユーザー名 input フィールドに autocomplete 属性を追加します。username と webauthn の両方をスペース区切りの値として含めます。
<input type="text" name="username" autocomplete="username webauthn" autofocus>
このフィールドに autofocus を追加すると、ページ読み込み時に自動入力プロンプトが自動的にトリガーされ、利用可能なパスワードとパスキーがすぐに表示されます。
特徴検出
条件付き WebAuthn API 呼び出しを呼び出す前に、次のことを確認します。
- ブラウザは
PublicKeyCredentialで WebAuthn をサポートしています。
- ブラウザは
PublicKeyCredential.getClientCapabilities()による機能検出をサポートしています。
- ブラウザが
conditionalGetで WebAuthn 条件付き UI をサポートしている。
次のスニペットは、ブラウザがこれらの機能をサポートしているかどうかを確認する方法を示しています。
if (window.PublicKeyCredential && PublicKeyCredential.getClientCapabilities) {
const capabilities = await PublicKeyCredential.getClientCapabilities();
// Check if conditional mediation is available.
if (capabilities.conditionalGet === true) {
// The browser supports conditional mediation.
}
}
バックエンドから情報を取得する
バックエンドは、navigator.credentials.get() 呼び出しを開始するために、フロントエンドにいくつかのオプションを提供する必要があります。通常、これらのオプションはサーバーのエンドポイントから JSON オブジェクトとして取得されます。
オプション オブジェクトの主なプロパティは次のとおりです。
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() Promise が解決されます。これにより、 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 プロパティ(rawId、clientDataJSON、authenticatorData、signature、userHandle など)から 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
});
署名の検証
バックエンド サーバーは、公開鍵認証情報を受け取ったら、その信頼性を検証する必要があります。これには以下が含まれます。
- 認証情報のデータを解析する。
- 認証情報の
idに関連付けられた保存済みの公開鍵を検索します。 - 受信した
signatureを保存された公開鍵と照合して検証します。 - チャレンジやオリジンなどの他のデータを検証します。
これらの暗号オペレーションを安全に処理するには、サーバーサイドの FIDO/WebAuthn ライブラリを使用することをおすすめします。オープンソース ライブラリは、awesome-webauthn GitHub リポジトリで確認できます。
署名と他のすべてのアサーションが有効な場合、サーバーはユーザーをログインさせることができます。サーバーサイド検証の詳細な手順については、サーバーサイドのパスキー認証をご覧ください。
一致する認証情報がバックエンドで見つからない場合にシグナルを送信する
ログイン時にバックエンド サーバーで一致する ID の認証情報が見つからない場合、ユーザーが以前にサーバーからこのパスキーを削除したものの、パスキー プロバイダからは削除していない可能性があります。この不一致により、パスキー プロバイダがサイトで機能しなくなったパスキーを提案し続けると、ユーザー エクスペリエンスが混乱する可能性があります。これを改善するには、パスキー プロバイダに孤立したパスキーを削除するよう通知する必要があります。
Webauthn Signal API の一部である PublicKeyCredential.signalUnknownCredential() メソッドを使用すると、指定された認証情報が削除されたか存在しないことをパスキー プロバイダに通知できます。サーバーが(たとえば、404 などの特定の HTTP ステータス コードで)提示された認証情報 ID が不明であることを示す場合は、クライアントサイドでこの静的メソッドを呼び出します。このメソッドに 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 要素)でログインした後に、パスキーを自動的に作成します(条件付き作成)。
- ユーザーがクロスデバイス パスキーでログインしている場合は、ローカル パスキーの作成を求めるプロンプトを表示します。
- ログイン後または変更が発生したときに、利用可能なパスキーのリストと更新されたユーザーの詳細(ユーザー名、表示名)をプロバイダに通知します。