既存のパスワード ユーザーに対応しながら、パスキーを利用するログイン エクスペリエンスを構築します。
パスキーはパスワードに代わるもので、ウェブ上のユーザー アカウントの安全性、シンプルさ、使いやすさを高めます。ただし、パスワード ベースの認証からパスキーベースの認証に移行すると、ユーザー エクスペリエンスが複雑になる可能性があります。フォームの自動入力を使用してパスキーを提案すると、統一されたエクスペリエンスの作成に役立ちます。
パスキーでログインするためにフォームの自動入力を使用する理由
パスキーを使用すると、ユーザーは指紋、顔認証、またはデバイスの PIN を使用するだけでウェブサイトにログインできます。
理想的には、パスワード ユーザーは存在せず、認証フローはシングル ログインボタンのようなシンプルなものになります。ユーザーがボタンをタップすると、アカウント選択ダイアログがポップアップ表示されます。ユーザーはアカウントを選択し、画面のロックを解除して確認してログインできます。
ただし、パスワードからパスキーベースの認証への移行は困難な場合があります。ユーザーがパスキーに切り替えたとしても、パスワードを使用するユーザーは引き続き存在します。ウェブサイトは、両方のタイプのユーザーに対応する必要があります。ユーザーがパスキーに切り替えたサイトを覚えておくことは望ましくありません。そのため、どの方法を使用するかを前もって選択するようユーザーに求めることは、UX の低下につながります。
パスキーも新しい技術です。ウェブサイトでは、そうしたポリシーについて説明し、ユーザーが安心して使用できるかどうかを確認することは、容易なことではありません。どちらの問題も、使い慣れたユーザー エクスペリエンスでパスワードの自動入力を行うことで解決できます。
条件付き UI
パスキーとパスワードの両方のユーザーにとって効率的なユーザー エクスペリエンスを構築するために、自動入力の候補にパスキーを含めることができます。これは条件付き UI と呼ばれ、WebAuthn 標準の一部です。
ユーザーがユーザー名の入力フィールドをタップすると、すぐに自動入力候補のダイアログがポップアップ表示され、保存されているパスキーとパスワードの自動入力の候補がハイライト表示されます。その後、ユーザーはアカウントを選択し、デバイスの画面ロックを使用してログインできます。
これにより、ユーザーは何も変更していないかのように既存のフォームを使用してウェブサイトにログインできます。ただし、パスキーがあれば、そのセキュリティ上のメリットもあります。
仕組み
パスキーで認証するには、WebAuthn API を使用します。
パスキー認証フローには、ユーザー、
- バックエンド: 公開鍵とパスキーに関するその他のメタデータを格納するアカウント データベースを保持するバックエンド サーバー。
- フロントエンド: ブラウザと通信し、バックエンドにフェッチ リクエストを送信します。
- ブラウザ: JavaScript を実行しているユーザーのブラウザ。
- Authenticator: パスキーを作成して保存するユーザーの認証システム。これは、ブラウザと同じデバイス(Windows Hello を使用している場合など)の場合もあれば、スマートフォンなどの別のデバイスにある場合もあります。
- ユーザーがフロントエンドに到達するとすぐに、バックエンドにパスキーによる認証チャレンジをリクエストし、
navigator.credentials.get()
を呼び出してパスキーによる認証を開始します。これはPromise
を返します。 - ユーザーがログイン フィールドにカーソルを置くと、パスキーを含むパスワード自動入力ダイアログがブラウザに表示されます。ユーザーがパスキーを選択すると、認証ダイアログが表示されます。
- ユーザーがデバイスの画面ロックを使用して本人確認を行うと、Promise が解決されて公開鍵認証情報がフロントエンドに返されます。
- フロントエンドがバックエンドに公開鍵認証情報を送信します。バックエンドは、データベース内にある、一致したアカウントの公開鍵と照らし合わせて署名を検証します。成功すると、ユーザーはログインされます。
前提条件
条件付き WebAuthn UI は、iOS 16、iPadOS 16、macOS Ventura の Safari で一般公開されています。Android、macOS、Windows 11 22H2 の Chrome でもご利用いただけます。
フォームの自動入力でパスキーを使用して認証する
ユーザーがログインを希望する場合は、条件付きの WebAuthn get
呼び出しを行い、自動入力の候補にパスキーを含めることができることを示すことができます。WebAuthn の navigator.credentials.get()
API への条件付き呼び出しでは UI が表示されず、ユーザーが自動入力の候補からログインに使用するアカウントを選択するまで保留状態のままになります。ユーザーがパスキーを選択すると、ログイン フォームに入力するのではなく、ブラウザが認証情報を使用して Promise を解決します。ユーザーのログインはページの役割になります
フォームの入力フィールドへのアノテーションの追加
必要に応じて、ユーザー名の input
フィールドに autocomplete
属性を追加します。
トークンとして username
と webauthn
を追加して、パスキーを提案できるようにします。
<input type="text" name="username" autocomplete="username webauthn" ...>
機能検出
条件付きの WebAuthn API 呼び出しを呼び出す前に、次の点を確認してください。
- ブラウザは WebAuthn をサポートしています。
- ブラウザは WebAuthn 条件付き UI をサポートしています。
// 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
}
}
RP サーバーからチャレンジを取得する
navigator.credentials.get()
を呼び出す必要があるチャレンジを RP サーバーから取得します。
challenge
: サーバーで生成されたチャレンジで、ArrayBuffer に格納されます。これはリプレイ攻撃を防ぐために必要です。ログイン試行のたびに新しい本人確認情報を生成し、一定期間が経った後、またはログイン試行が検証に失敗した後は、その本人確認を無視してください。これは CSRF トークンのようなものと考えてください。allowCredentials
: この認証に使用できる認証情報の配列。空の配列を渡して、ブラウザに表示されたリストから利用可能なパスキーをユーザーが選択できるようにします。userVerification
: デバイスの画面ロックを使用したユーザー確認が"required"
、"preferred"
、"discouraged"
のいずれであるかを示します。デフォルトは"preferred"
です。この場合、認証システムはユーザー確認をスキップできます。"preferred"
に設定するか、プロパティを省略します。
conditional
フラグを指定して WebAuthn API を呼び出し、ユーザーを認証します。
navigator.credentials.get()
を呼び出して、ユーザー認証の待機を開始します。
// To abort a WebAuthn call, instantiate an `AbortController`.
const abortController = new AbortController();
const publicKeyCredentialRequestOptions = {
// Server generated challenge
challenge: ****,
// The same RP ID as used during registration
rpId: 'example.com',
};
const credential = await navigator.credentials.get({
publicKey: publicKeyCredentialRequestOptions,
signal: abortController.signal,
// Specify 'conditional' to activate conditional UI
mediation: 'conditional'
});
rpId
: RP ID はドメインであり、ウェブサイトではドメインまたは登録可能なサフィックスのいずれかを指定できます。この値は、パスキーの作成時に使用された rp.id と一致する必要があります。
リクエストを条件付きにするには、必ず mediation: 'conditional'
を指定してください。
返された公開鍵認証情報を RP サーバーに送信する
ユーザーがアカウントを選択し、デバイスの画面ロックを使用して同意すると、Promise は解決され、RP フロントエンドに PublicKeyCredential
オブジェクトが返されます。
Promise は、さまざまな理由で拒否されます。Error
オブジェクトの name
プロパティに応じて、それに応じてエラーを処理する必要があります。
NotAllowedError
: ユーザーがオペレーションをキャンセルしました。- その他の例外: 予期しないエラーが発生しました。ブラウザにエラー ダイアログが表示されます。
公開鍵認証情報オブジェクトには、次のプロパティが含まれています。
id
: base64url でエンコードされた認証済みパスキー認証情報の ID。rawId
: 認証情報 ID の ArrayBuffer バージョン。response.clientDataJSON
: クライアント データの ArrayBuffer。このフィールドには、チャレンジや RP サーバーで検証する必要があるオリジンなどの情報が含まれます。response.authenticatorData
: 認証システムデータの ArrayBuffer。このフィールドには、RP ID などの情報が含まれます。response.signature
: 署名の ArrayBuffer。この値は認証情報のコアであり、サーバーで検証する必要があります。response.userHandle
: 作成時に設定されたユーザー ID を含む ArrayBuffer。サーバーが使用する ID 値をサーバーが選択する必要がある場合、またはバックエンドが認証情報 ID のインデックスを作成しないようにする場合は、認証情報 ID の代わりにこの値を使用できます。authenticatorAttachment
: この認証情報がローカル デバイスから取得された場合は、platform
を返します。それ以外の場合はcross-platform
。特にユーザーがスマートフォンを使用してログインした場合。ユーザーがスマートフォンを使用してログインする必要がある場合は、ローカル デバイスでパスキーを作成するようユーザーに促すことを検討してください。type
: このフィールドは常に"public-key"
に設定されます。
ライブラリを使用して RP サーバー上の公開鍵認証情報オブジェクトを処理する場合は、オブジェクトを base64url で部分的にエンコードしてから、全体をサーバーに送信することをおすすめします。
署名の検証
サーバーで公開鍵認証情報を受け取ったら、FIDO ライブラリに渡してオブジェクトを処理します。
id
プロパティで一致する認証情報 ID を検索します(ユーザー アカウントを特定する必要がある場合は、認証情報の作成時に指定した user.id
である userHandle
プロパティを使用します)。保存されている公開鍵で認証情報の signature
を検証できるかどうかを確認します。そのためには、独自のコードを記述するのではなく、サーバー側のライブラリまたはソリューションを使用することをおすすめします。オープンソース ライブラリは、Awesome-webauth GitHub リポジトリにあります。
一致する公開鍵で認証情報が検証されたら、ユーザーをログインさせます。