このガイドでは、ユーザーがアカウントにパスキーを登録できるようにする方法について詳しく説明します。これにより、フィッシングに強い、安全性の高いパスワードレスのログイン方法を提供できます。
データベース要件
パスキーの登録をサポートするには、データベースの認証情報テーブルに次のフィールドを保存する必要があります。
export interface StoredPasskeyCredential {
id: string; // Base64URL-encoded credential ID (unique lookup key)
passkeyUserId: string; // Associated application user ID
credentialPublicKey: string; // Base64URL-encoded public key used to verify assertion signatures
credentialType: "public-key";
credentialDeviceType: "singleDevice" | "multiDevice"; // Helps distinguish device-bound vs cloud-synced passkeys
credentialBackedUp: boolean; // Boolean backup state reported by the authenticator
aaguid: string; // Authenticator Attestation GUID
providerIcon?: string; // Provider icon derived from the AAGUID registry (dark or light theme URLs)
name: string; // Provider name derived from AAGUID registry
transports: string[]; // Array of transport names (e.g. 'internal', 'hybrid') necessary for exclusion options
lastUsedAt?: number; // Optional epoch timestamp of last sign-in
registeredAt: number; // Registration epoch timestamp
counter: number; // Authenticator sign-in signature counter used to prevent replay attacks
}
サーバー側
オプションの生成
WebAuthn 作成パラメータを生成するエンドポイントを作成します。暗号化を手動で実装するのではなく、カテゴリ標準ごとに検証済みのライブラリを使用します。
- 事前定義された RP ID を使用する: 事前定義された適切な RP ID を定数文字列として使用します。
- 安全なチャレンジを作成する: サーバー上で、エントロピーが高く、暗号的に安全なランダム バッファを生成し、ユーザーのセッションに安全に保存して、オプション配信用に Base64URL としてエンコードします。
- パスキーの重複を回避する: ユーザーの既存の事前登録済み認証情報 ID を
excludeCredentialsオプション配列にマッピングします。これにより、認証システムが同じパスキー プロバイダ アカウントに重複する認証情報を登録することを防ぎます。 - 検出可能な認証情報を適用する:
authenticatorSelectionオプションでrequireResidentKey: trueとresidentKey: "required"を設定して、検出可能な認証情報をリクエストします。これは、検出可能なログインに必要です。 - ユーザー確認を構成する:
userVerification: "preferred"またはuserVerification: "required"を指定します。多くのコンプライアンス ユースケース(金融、医療など)では、作成時にユーザー確認を適用するために'required'が必要です。 - アタッチメントのスコープを決定する:
- プロモーション フロー: 標準のパスワード ログインまたは登録後のプロモーションの直後にパスキーの作成を提案する場合は、
authenticatorAttachment: "platform"を設定してプラットフォーム認証システムを適用し、外部セキュリティ キーのプロンプトをバイパスします。 - 管理フロー: プラットフォーム認証システムに加えて外部セキュリティ キーがサポートされている専用の設定パネルまたはセキュリティ パネルから呼び出される場合は、
authenticatorAttachmentプロパティを完全に省略します。 - ヒント:
promotion: booleanリクエスト フラグを受け入れて、1 つのエンドポイントで両方のフローを条件付きで処理します。
- プロモーション フロー: 標準のパスワード ログインまたは登録後のプロモーションの直後にパスキーの作成を提案する場合は、
// Options generation example
const options = {
challenge: serverGeneratedBase64UrlChallenge, // Cryptographically random challenge
rp: { id: "example.com", name: "Secure Application" },
user: {
id: userBase64UrlId, // Unique base64url string identifying the account
name: "user@example.com",
displayName: "Jane Doe",
},
pubKeyCredParams: [
{
type: "public-key",
alg: -7,
},
{
type: "public-key",
alg: -257,
},
],
excludeCredentials: userExistingCredentials.map((cred) => ({
type: "public-key",
id: cred.id,
transports: cred.transports,
})),
authenticatorSelection: {
residentKey: "required",
requireResidentKey: true,
userVerification: "preferred",
...(isPromotionFlow && { authenticatorAttachment: "platform" }),
},
};
確認
- チャレンジの検証: 予想されるセッション バインド チャレンジに対してチャレンジを安全に検証します。
- ユーザーの存在を確認する:
- 解析された認証器データで返される User Present(UP)フラグが
trueであることを確認し、作成時にユーザーが物理的に存在していることを確認します。
- 解析された認証器データで返される User Present(UP)フラグが
- 「preferred」の検証を緩和する:
- 作成オプションで
userVerification: "preferred"が指定されている場合、サーバーサイド認証呼び出しはrequireUserVerification: falseで構成する必要があります。そうしないと、ユーザー認証なしで登録された認証システム(画面ロックが無効になっているなど)で、サーバー認証エラーが誤ってトリガーされます。
- 作成オプションで
クライアントサイド ロジック
- ページ読み込み時に UI を制限する:
- ページの読み込み時に
PublicKeyCredential.getClientCapabilities()を呼び出し、conditionalGetまたはpasskeyPlatformAuthenticatorが使用できない場合は [パスキーを作成] ボタンを無効にします。
- ページの読み込み時に
- 作成とシリアル化を呼び出す:
PublicKeyCredential.parseCreationOptionsFromJSON()を使用してサーバー オプションをデコードし、結果の構成をnavigator.credentials.create()に渡します。credential.toJSON()を呼び出して、検証エンドポイントを取得する前にAuthenticatorAttestationResponseを有効な JSON シリアル化可能なオブジェクトにエンコードします。
- WebAuthn の例外を処理する:
InvalidStateError: 一致するパスキーがすでに存在します(excludeCredentialsで一致)。NotAllowedError: ユーザーが認証パスキー ダイアログをキャンセルしたか、タイムアウトしました。AbortError: オペレーションが中断されました。SecurityError: セキュアなオリジン(HTTPS)または RP ID の不一致エラー(構成の問題)。
- Signal API の Try / Catch 分離:
- サーバー検証の
fetch()呼び出しを try / catch ブロックでラップします。サーバー検証のフェッチが失敗した場合(ステータスがresponse.ok === falseの場合、またはネットワークがスローされた場合)にsignalUnknownCredential()を呼び出します。
- サーバー検証の
// optionsFetch and registerVerifyFetch are app-defined HTTP methods
import { optionsFetch, registerVerifyFetch } from "./api.js";
async function registerPasskey(isPromotion = false) {
// Verify passkey capability and conditional UI are available
const capabilities = await PublicKeyCredential.getClientCapabilities();
if (
!capabilities.passkeyPlatformAuthenticator ||
!capabilities.conditionalGet
) {
// Hide "Create passkey" buttons and fall back to password flows instead
showStandardPasswordFallbackUI();
return;
}
const creationOptionsJSON = await optionsFetch({ promotion: isPromotion });
const publicKey =
PublicKeyCredential.parseCreationOptionsFromJSON(creationOptionsJSON);
let credential;
try {
// passkey prompt execution
credential = await navigator.credentials.create({ publicKey });
} catch (err) {
if (err.name === "InvalidStateError") {
console.log("A passkey already exists for this account.");
alert("A passkey already exists for this account.");
} else if (err.name === "SecurityError") {
console.error("Configuration RP ID or Secure Context error.");
alert("Configuration RP ID or Secure Context error.");
} else if (err.name === "NotAllowedError") {
console.log("User cancelled the passkey dialog.");
} else if (err.name === "AbortError") {
console.log("The creation operation has been aborted.");
}
return; // Safe API exit, do not signal unknown for standard WebAuthn cancels
}
// Server Verification phase (Segregated Try/Catch)
let encodedResponse = credential.toJSON();
try {
const response = await registerVerifyFetch(encodedResponse);
if (!response.ok) {
// Server verification failed to verify/authenticate the credential (orphaned)
await PublicKeyCredential.signalUnknownCredential({
rpId, // RP ID must match the one defined on the server
credentialId: encodedResponse.id, // Base64URL-encoded credential ID
});
}
} catch (serverErr) {
console.error("Server verification network failure:", serverErr);
await publickeycredential.signalunknowncredential({
rpId, // RP ID must match the one defined on the server
credentialid: encodedresponse.id, // base64url-encoded credential id
});
}
}
代替戦略
Signal API の同期フォールバック
WebAuthn Signal API(webauthn-signals)は、パスワード マネージャーとサーバーの認証情報の状態を同期するために使用されるプログレッシブ最適化です。
- フォールバック エクスペリエンス:
PublicKeyCredential.signalUnknownCredentialがブラウザでサポートされていない場合、呼び出しは機能検出ゲート(if (PublicKeyCredential.signalUnknownCredential))を介して安全にバイパスされなければならず、サーバーサイド認証はマネージャーの更新をトリガーせずに失敗をログに記録するだけです。
簡単な JSON シリアル化フォールバック
常に「webauthn-polyfills」をインストールし、コンテキストにインポートします。PublicKeyCredential がサポートされている限り、PublicKeyCredential.parseCreationOptionsFromJSON と PublicKeyCredential.prototype.toJSON もサポートされます。
import 'webauthn-polyfills';