パスキーを使用すると、ユーザー アカウントの安全性、シンプルさ、使いやすさを向上させることができます。
パスキーを使用すると、セキュリティが強化され、ログインが簡素化され、パスワードが置き換えられます。ユーザーが覚えて手動で入力する必要がある通常のパスワードとは異なり、パスキーは生体認証や PIN などのデバイスの画面ロック メカニズムを使用するため、フィッシングのリスクや認証情報の盗難を軽減できます。
パスキーは、Google パスワード マネージャーや iCloud キーチェーンなどのパスキー プロバイダを使用してデバイス間で同期されます。
パスキーを作成して、必要なメタデータとともに秘密鍵をパスキー プロバイダに安全に保存し、その公開鍵を認証用にサーバーに保存する必要があります。秘密鍵は、有効なドメインでユーザー確認が完了した後に署名を発行するため、パスキーはフィッシングに耐性があります。公開鍵は機密性の高い認証情報を保存せずに署名を検証するため、パスキーは認証情報の盗難に強い仕組みになっています。
パスキーの作成の仕組み
ユーザーがパスキーを使用してログインできるようにするには、パスキーを作成してユーザー アカウントに関連付け、その公開鍵をサーバーに保存する必要があります。
パスキーの作成をユーザーに依頼する必要があるのは、次のいずれかの場合です。
- 登録中または登録後。
- ログイン後
- 別のデバイスのパスキーを使用してログインした後(つまり、[authenticatorAttachment](https://web.dev/articles/passkey-form-autofill#authenticator-attachment)がcross-platformの場合)。
- ユーザーがパスキーを管理できる専用のページ。
パスキーを作成するには、WebAuthn API を使用します。
パスキー登録フローの 4 つのコンポーネントは次のとおりです。
- バックエンド: 公開鍵などのユーザー アカウントの詳細を保存します。
- フロントエンド: ブラウザと通信し、バックエンドから必要なデータを取得します。
- ブラウザ: JavaScript を実行し、WebAuthn API とやり取りします。
- パスキー プロバイダ: パスキーを作成して保存します。通常は、Google パスワード マネージャーなどのパスワード マネージャーまたはセキュリティ キーです。
 
  パスキーを作成する前に、システムが次の前提条件を満たしていることを確認してください。
- ユーザー アカウントは、短い時間内に安全な方法(メール、電話認証、ID 連携など)で確認されます。 
- フロントエンドとバックエンドは、安全に通信して認証情報データを交換できます。 
- ブラウザが WebAuthn とパスキーの作成をサポートしている。 
以降のセクションでは、これらのほとんどを確認する方法について説明します。
システムがこの条件を満たすと、パスキーを作成するために次のプロセスが実行されます。
- ユーザーがアクションを開始すると(パスキー管理ページで [パスキーを作成する] ボタンをクリックしたときや、登録を完了した後など)、システムはパスキーの作成プロセスをトリガーします。
- フロントエンドは、重複を防ぐために、ユーザー情報、チャレンジ、認証情報 ID など、必要な認証情報データをバックエンドからリクエストします。
- フロントエンドは navigator.credentials.create()を呼び出して、バックエンドの情報を使用してパスキーを生成するようデバイスのパスキー プロバイダにプロンプトを表示します。この呼び出しは Promise を返します。
- ユーザーのデバイスが、生体認証方法、PIN、またはパターンを使用してユーザーを認証し、パスキーを作成します。
- パスキー プロバイダはパスキーを作成し、公開鍵認証情報をフロントエンドに返して、プロミスを解決します。
- フロントエンドは、生成された公開鍵認証情報をバックエンドに送信します。
- バックエンドは、将来の認証に使用するために公開鍵などの重要なデータを保存します。
- バックエンドは、パスキーの作成を確認して不正なアクセスを検出するために、ユーザーに(メールなどを使用して)通知します。
このプロセスにより、ユーザーはパスキーを安全かつシームレスに登録できます。
互換性
ほとんどのブラウザは WebAuthn をサポートしていますが、一部の機能はサポートされていません。ブラウザと OS の互換性の詳細については、passkeys.dev をご覧ください。
新しいパスキーを作成する
新しいパスキーを作成するには、フロントエンドで次のプロセスを実行する必要があります。
以降のセクションでは、その方法について説明します。
互換性の確認
[新しいパスキーを作成する] ボタンを表示する前に、フロントエンドは次の条件を確認する必要があります。
- ブラウザが PublicKeyCredentialで WebAuthn をサポートしている。
- デバイスは PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable()でプラットフォーム認証器をサポートしています(パスキーを作成してパスキーで認証できます)。
- ブラウザが PublicKeyCredenital.isConditionalMediationAvailable()で WebAuthn 条件付き UI をサポートしている。
次のコード スニペットは、パスキー関連のオプションを表示する前に互換性を確認する方法を示しています。
// Availability of `window.PublicKeyCredential` means WebAuthn is usable.  
// `isUserVerifyingPlatformAuthenticatorAvailable` means the feature detection is usable.  
// `isConditionalMediationAvailable` means the feature detection is usable.  
if (window.PublicKeyCredential &&  
    PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable &&  
    PublicKeyCredential.isConditionalMediationAvailable) {  
  // Check if user verifying platform authenticator is available.  
  Promise.all([  
    PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable(),  
    PublicKeyCredential.isConditionalMediationAvailable(),  
  ]).then(results => {  
    if (results.every(r => r === true)) {  
      // Display "Create a new passkey" button  
    }  
  });  
}  
この例では、すべての条件が満たされている場合にのみ、[Create a new passkey] ボタンが表示されます。
バックエンドから情報を取得する
ユーザーがボタンをクリックすると、バックエンドから必要な情報を取得して navigator.credentials.create() を呼び出します。
次のコード スニペットは、navigator.credentials.create() の呼び出しに必要な情報を含む JSON オブジェクトを示しています。
// Example `PublicKeyCredentialCreationOptions` contents
{
  challenge: *****,
  rp: {
    name: "Example",
    id: "example.com",
  },
  user: {
    id: *****,
    name: "john78",
    displayName: "John",
  },
  pubKeyCredParams: [{
    alg: -7, type: "public-key"
  },{
    alg: -257, type: "public-key"
  }],
  excludeCredentials: [{
    id: *****,
    type: 'public-key',
    transports: ['internal'],
  }],
  authenticatorSelection: {
    authenticatorAttachment: "platform",
    requireResidentKey: true,
  }
}
オブジェクト内の Key-Value ペアには、次の情報が保持されます。
- challenge: この登録用にサーバーによって生成された ArrayBuffer 内のチャレンジ。
- rp.id: RP ID(リレーリング パーティ ID)、ドメイン、ウェブサイトは、ドメインまたは登録可能なサフィックスを指定できます。たとえば、RP のオリジンが- https://login.example.com:1337の場合は、RP ID として- login.example.comまたは- example.comのいずれかを指定できます。RP ID が- example.comとして指定されている場合、ユーザーは- login.example.comで認証を行うことができます。また、- example.comの任意のサブドメインでも認証を行うことができます。詳細については、関連オリジン リクエストを使用してサイト間でパスキーの再利用を許可するをご覧ください。
- rp.name: RP(証明書利用者)の名前。これは WebAuthn L3 では非推奨ですが、互換性のため含まれています。
- user.id: アカウントの作成時に生成される ArrayBuffer の一意のユーザー ID。ユーザー名とは異なり、編集可能なものではなく、永続的なものである必要があります。ユーザー ID はアカウントを識別しますが、個人を特定できる情報(PII)は含めないでください。システムにユーザー ID がすでにある可能性がありますが、必要に応じて、パスキー専用のユーザー ID を作成して PII が含まれないようにします。
- user.name: ユーザーが認識できるアカウントの一意の識別子(メールアドレスやユーザー名など)。この識別子はアカウント選択画面に表示されます
- user.displayName: アカウントのユーザー フレンドリーな名前。必須です。一意である必要はなく、ユーザーが選択した名前でも構いません。このフィールドに含めるのに適した値がサイトにない場合は、空の文字列を渡してください。ブラウザによっては、アカウント選択画面にこの値が表示されることがあります。
- pubKeyCredParams: RP(信頼できるパーティ)でサポートされている公開鍵アルゴリズムを指定します。- [{alg: -7, type: "public-key"},{alg: -257, type: "public-key"}]に設定することをおすすめします。この設定は、P-256 の ECDSA と RSA PKCS#1 がサポートされていることを意味します。これらをサポートすることで、全範囲をカバーできます。
- excludeCredentials: すでに登録されている認証情報 ID のリスト。すでに登録されている認証情報 ID のリストを指定することで、同じデバイスが 2 回登録されないようにします。- transportsメンバーが指定されている場合、メンバーには、各認証情報の登録時に- getTransports()関数を呼び出した結果が含まれている必要があります。
- authenticatorSelection.authenticatorAttachment: このパスキーの作成が、ログイン後のプロモーションなど、パスワードからのアップグレードである場合は、- hint: ['client-device']とともに- "platform"に設定します。- "platform"は、USB セキュリティ キーなどの挿入を求めないプラットフォーム認証システム(プラットフォーム デバイスに埋め込まれた認証システム)を RP が希望していることを示します。ユーザーはパスキーを簡単に作成できます。
- authenticatorSelection.requireResidentKey: ブール値- trueに設定します。検出可能な認証情報(ローカルキー)は、ユーザー情報をパスキーに保存し、認証時にユーザーがアカウントを選択できるようにします。
- authenticatorSelection.userVerification: デバイスの画面ロックを使用するユーザー確認が- "required"、- "preferred"、- "discouraged"のいずれであるかを示します。デフォルトは- "preferred"です。つまり、認証システムはユーザー確認をスキップする場合があります。- "preferred"に設定するか、プロパティを省略します。
サーバー上でオブジェクトを作成して、ArrayBuffer を Base64URL でエンコードし、フロントエンドから取得することをおすすめします。これにより、PublicKeyCredential.parseCreationOptionsFromJSON() を使用してペイロードをデコードし、navigator.credentials.create() に直接渡すことができます。
次のコード スニペットは、パスキーの作成に必要な情報を取得してデコードする方法を示しています。
// Fetch an encoded `PubicKeyCredentialCreationOptions` from the server.
const _options = await fetch('/webauthn/registerRequest');
// Deserialize and decode the `PublicKeyCredentialCreationOptions`.
const decoded_options = JSON.parse(_options);
const options = PublicKeyCredential.parseCreationOptionsFromJSON(decoded_options);
...
WebAuthn API を呼び出してパスキーを作成する
navigator.credentials.create() を呼び出して新しいパスキーを作成します。API はプロミスを返します。プロミスは、ユーザーが操作してモーダル ダイアログを表示するのを待ちます。
// Invoke WebAuthn to create a passkey.
const credential = await navigator.credentials.create({
  publicKey: options
});
返された公開鍵認証情報をバックエンドに送信する
デバイスの画面ロックを使用してユーザーが確認されると、パスキーが作成され、プロミスが解決して PublicKeyCredential オブジェクトがフロントエンドに返されます。
プロミスはさまざまな理由で拒否されることがあります。これらのエラーは、Error オブジェクトの name プロパティを確認することで処理できます。
- InvalidStateError: デバイスにパスキーがすでに存在します。ユーザーにエラー ダイアログは表示されません。サイトはこれをエラーとして扱うべきではありません。お客様はローカル デバイスを登録することを希望しており、デバイスは登録されています。
- NotAllowedError: ユーザーがオペレーションをキャンセルしました。
- AbortError: オペレーションが中止されました。
- その他の例外: 予期しないエラーが発生しました。ブラウザにエラー ダイアログが表示されます。
公開鍵認証情報オブジェクトには、次のプロパティが含まれています。
- id: 作成されたパスキーの Base64URL でエンコードされた ID。この ID によって、ブラウザは認証時に一致するパスキーがデバイス内にあるかどうかを判別できます。この値は、バックエンドのデータベースに保存する必要があります。
- rawId: 認証情報 ID の ArrayBuffer バージョン。
- response.clientDataJSON: ArrayBuffer でエンコードされたクライアント データ。
- response.attestationObject: ArrayBuffer でエンコードされた構成証明オブジェクト。これには、RP ID、フラグ、公開鍵などの重要な情報が含まれます。
- authenticatorAttachment: パスキー対応デバイスでこの認証情報が作成されると、- "platform"が返されます。
- type: このフィールドは常に- "public-key"に設定されます。
.toJSON() メソッドでオブジェクトをエンコードし、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/registerResponse', {
  method: 'post',
  credentials: 'same-origin',
  body: result
});
...
認証情報を保存する
バックエンドで公開鍵認証情報を受け取ったら、独自のコードを使用して公開鍵認証情報を処理するのではなく、サーバーサイド ライブラリまたはソリューションを使用することをおすすめします。
認証情報から取得した情報をデータベースに保存して、後で使用できます。
保存することをおすすめするプロパティは次のとおりです。
- 認証情報 ID: 公開鍵認証情報とともに返される認証情報 ID。
- 認証情報の名前: 認証情報の名前。作成したパスキー プロバイダの名前(AAGUID に基づいて識別可能)で名前を付けます。
- ユーザー ID: パスキーの作成に使用したユーザー ID。
- 公開鍵: 公開鍵認証情報とともに返される公開鍵。これは、パスキーの構成証明を検証するために必要です。
- 作成日時: パスキーの作成日時を記録します。これはパスキーの特定に役立ちます。
- 最終使用日時: ユーザーがパスキーを使用してログインした最終日時を記録します。これは、ユーザーが使用した(または使用しなかった)パスキーを特定するのに役立ちます。
- AAGUID: パスキー プロバイダの一意の識別子。
- バックアップの利用資格フラグ: デバイスがパスキーの同期の対象である場合は true です。この情報は、パスキー管理ページで同期可能なパスキーとデバイスにバインドされた(同期不可)パスキーを識別するのに役立ちます。
詳しくは、サーバーサイド パスキーの登録をご覧ください。
登録に失敗した場合はシグナルを送信する
パスキーの登録に失敗すると、ユーザーの混乱を招く可能性があります。パスキー プロバイダにパスキーがあり、ユーザーが使用できるにもかかわらず、関連付けられた公開鍵がサーバー側に保存されていない場合、パスキーを使用したログイン試行は成功せず、トラブルシューティングが困難になります。該当する場合は、必ずお客様に伝えてください。
このような状態を防ぐには、Signal API を使用してパスキー プロバイダに不明なパスキーを通知します。RP ID と認証情報 ID を指定して PublicKeyCredential.signalUnknownCredential() を呼び出すと、RP は指定された認証情報が削除されたか存在しないことをパスキー プロバイダに通知できます。このシグナルをどのように処理するかはパスキー プロバイダ次第ですが、サポートされている場合は、関連付けられたパスキーが削除される必要があります。
// 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.
    ...
  }
}
Signal API の詳細については、Signal API を使用して、パスキーとサーバー上の認証情報の整合性を維持するをご覧ください。
ユーザーに通知を送信する
パスキーが登録されたときに通知(メールなど)を送信すると、ユーザーはアカウントへの不正アクセスを検出できます。攻撃者がユーザーに知られることなくパスキーを作成した場合、パスワードを変更しても、パスキーは今後の不正使用に使用される可能性があります。この通知はユーザーに警告し、この事態を防ぐのに役立ちます。
チェックリスト
- パスキーの作成を許可する前に、ユーザーを確認します(できればメールまたは安全な方法で)。
- excludeCredentialsを使用して、同じパスキー プロバイダの重複するパスキーが作成されないようにします。
- AAGUID を保存して、パスキー プロバイダを特定し、ユーザーの認証情報に名前を付けます。
- パスキーの登録が失敗した場合は、PublicKeyCredential.signalUnknownCredential()で通知します。
- アカウントのパスキーの作成と登録後に、ユーザーに通知を送信します。
リソース
次のステップ: フォームの自動入力でパスキーを使用してログインする。
