自社のWebサービスやアプリにパスキー(WebAuthn/FIDO2)でのログインを組み込もうとすると、「フロントでnavigator.credentialsを呼ぶところまでは分かったが、サーバ側で何を検証し、何をデータベースに保存すればいいのか」で手が止まりがちです。フロントの呼び出しは分かっても、サーバ側の検証とデータ保存でつまずくのがパスキー実装のよくある場面です。
パスキーの実装は、登録(クレデンシャル作成)と認証(署名検証)の2つのセレモニーを、ブラウザ・サーバ・認証器の三者でやり取りする流れさえ掴めば、全体像は難しくありません。実装方式も、素のWebAuthnを自前で書く、SimpleWebAuthnなどのライブラリを使う、認証基盤サービスに任せる、の3通りから自社の要件に合わせて選べます。
本記事では、登録・認証それぞれの動くコード(フロント+サーバ)とやり取りの流れ、保存すべきデータとDB設計、RP IDやconditional UIといったつまずきやすい点までを通しで解説します。あわせて、実装を肩代わりする認証基盤・SDKも紹介するので、自前実装か外部サービスかの判断材料としてご活用ください。
本記事のコードは@simplewebauthn v14系、技術的な根拠はW3C WebAuthn仕様(Level 3)に準拠しています。
目次
パスキー実装の全体像(登録・認証の流れ)
ここでは、コードに入る前に、パスキーの登録と認証で誰が何をやり取りするのかを整理します。全体像を先に押さえておくと、後述のコードでサーバが何を検証しているのかが読み解きやすくなります。
パスキーは公開鍵暗号を使った認証です。登録時に認証器(スマートフォンやPCの生体認証、セキュリティキー)が鍵ペアを作り、秘密鍵は認証器の中に留まり、公開鍵だけがサーバに渡って保存されます。認証時は、サーバが送った使い捨ての乱数(challenge)に認証器が秘密鍵で署名し、サーバが保存済みの公開鍵で署名を検証します。秘密鍵がネットワークに出ないため、フィッシングやパスワード漏洩に強いのが特徴です。
登場人物は、W3C WebAuthn仕様のセレモニー定義に沿うと、ログインするユーザー、パスキーを提供する自社サービス(Relying Party、以下RP。公開鍵の保存・検証を担うサーバ側)、ユーザーの手元のクライアント(ブラウザのWebAuthn API)、そして鍵を生成・保管する認証器です。
仕様は登録・認証を「セレモニー(ceremony)」と呼び、これらが協調して1つのクレデンシャルを作成・利用する一連の手続きと位置づけています。
Registration Ceremony The ceremony where a user, a Relying Party, and the user’s client platform (containing or connected to at least one authenticator) work in concert to create a public key credential and associate it with a user account.
出典:Web Authentication: An API for accessing Public Key Credentials – Level 3|W3C Recommendation, 25 August 2026(https://www.w3.org/TR/webauthn-3/)
登録(パスキー作成)の流れ
- ブラウザがサーバに登録開始を要求し、サーバが登録オプション(challenge、RP ID、ユーザー情報、既存クレデンシャル一覧など)を発行する。
- ブラウザが
navigator.credentials.create()を呼び、認証器が生体認証などでユーザーを確認して鍵ペアを生成する。 - 認証器が公開鍵とクレデンシャルIDを含むレスポンス(attestationObject)を返し、ブラウザがサーバに送る。
- サーバがchallengeとoriginを検証し、公開鍵・クレデンシャルID・sign countをユーザーに紐付けて保存する。
認証(ログイン)の流れ
- ブラウザがサーバに認証開始を要求し、サーバが新しいchallengeを含む認証オプションを発行する。
- ブラウザが
navigator.credentials.get()を呼び、認証器がユーザーを確認して秘密鍵でchallengeに署名する。 - 認証器が署名(assertion)を返し、ブラウザがサーバに送る。
- サーバが保存済みの公開鍵で署名を検証し、challengeの一致とsign countを確認してログインを成立させる。
登録・認証それぞれで、4者がchallengeと署名をどうやり取りするかを図にすると次のようになります。

認証セレモニーの核心は、秘密鍵をサーバに渡さず、その所有を暗号学的に証明する点にあります。W3C仕様も次のように定義しています。
Authentication Ceremony The ceremony where a user, and the user’s client platform (containing or connected to at least one authenticator) work in concert to cryptographically prove to a Relying Party that the user controls the credential private key of a previously-registered public key credential.
出典:Web Authentication – Level 3|W3C Recommendation, 25 August 2026(https://www.w3.org/TR/webauthn-3/)
パスキー登録フローの実装(フロント+サーバ)
ここからは、実際のコードで登録フローを実装します。以下は@simplewebauthn/server(v14系)と@simplewebauthn/browser(v14系)を使った例です。challengeの生成・レスポンスの検証といった難所をライブラリに任せつつ、DB保存や画面は自社で持つ構成です。
まずサーバ側です。登録オプションの発行でchallengeを生成し、セッションなどに一時保存します。excludeCredentialsに登録済みクレデンシャルを渡すと、同じ認証器への二重登録を防げます。
// server: 登録オプションの発行
import {
generateRegistrationOptions,
verifyRegistrationResponse,
} from '@simplewebauthn/server';
const rpName = 'Example Corp';
const rpID = 'example.com'; // 登録するドメイン(開発時は 'localhost')
const origin = 'https://example.com'; // フロントのオリジン(開発時は 'http://localhost:3000' など)
app.post('/register/options', async (req, res) => {
const user = await getUser(req.session.userId);
const options = await generateRegistrationOptions({
rpName,
rpID,
userName: user.email,
attestationType: 'none',
excludeCredentials: user.passkeys.map((pk) => ({
id: pk.id,
transports: pk.transports,
})),
authenticatorSelection: {
residentKey: 'preferred',
userVerification: 'preferred',
},
});
req.session.currentChallenge = options.challenge; // 使い捨てで保持
req.session.webauthnUserID = options.user.id; // ユーザーハンドルを保存用に保持
res.json(options);
});
フロント側は、発行されたオプションをstartRegistration()に渡すだけです。v14系ではオプションを{ optionsJSON }の形で渡します。認証器の起動(生体認証など)はライブラリとブラウザが行います。
// browser: 登録
import { startRegistration } from '@simplewebauthn/browser';
async function registerPasskey() {
const optionsJSON = await fetch('/register/options', {
method: 'POST',
}).then((r) => r.json());
// 認証器が起動し、公開鍵クレデンシャルを生成
const attResp = await startRegistration({ optionsJSON });
const result = await fetch('/register/verify', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(attResp),
}).then((r) => r.json());
if (result.verified) alert('パスキーを登録しました');
}
実際のコードでは、ユーザーがプロンプトを閉じたときなどにstartRegistration()/startAuthentication()が例外(NotAllowedErrorなど)を投げるため、try/catchで囲んでキャンセル時の表示を用意します。ここでは流れを追いやすくするため省略しています。
最後に、返ってきたレスポンスをサーバで検証し、公開鍵などを保存します。verifyRegistrationResponse()がchallenge・origin・RP IDの一致とattestationの解析をまとめて行い、検証が通ればregistrationInfo.credentialに保存すべきデータが入ります。検証後はchallengeを破棄します。
// server: 登録レスポンスの検証と保存
app.post('/register/verify', async (req, res) => {
const user = await getUser(req.session.userId);
const verification = await verifyRegistrationResponse({
response: req.body,
expectedChallenge: req.session.currentChallenge,
expectedOrigin: origin,
expectedRPID: rpID,
});
if (!verification.verified) {
return res.status(400).json({ error: '検証に失敗しました' });
}
const { credential, credentialDeviceType, credentialBackedUp } =
verification.registrationInfo;
await savePasskey(user.id, {
id: credential.id, // クレデンシャルID
publicKey: credential.publicKey, // 公開鍵(署名検証に使う)
webauthnUserID: req.session.webauthnUserID, // ユーザーハンドル
counter: credential.counter, // sign count
transports: credential.transports,
deviceType: credentialDeviceType,
backedUp: credentialBackedUp,
});
req.session.currentChallenge = undefined; // challenge を破棄
res.json({ verified: true });
});
登録時にサーバが受け取るattestationObjectは、認証器データとアテステーションステートメントを含み、その中に公開鍵とクレデンシャルIDが入っています。自前実装ではこの解析を自作する必要がありますが、ライブラリを使えばverifyRegistrationResponse()が肩代わりします。
パスキー認証フローの実装(フロント+サーバ)
続いて認証(ログイン)です。登録と対になる流れで、サーバが発行したchallengeに認証器が署名し、サーバが公開鍵で検証します。サーバ側の認証オプション発行では、特定ユーザーに絞るならallowCredentialsを渡し、パスキー選択に任せる(ユーザー名を先に聞かない)なら空でも動きます。
// server: 認証オプションの発行
import {
generateAuthenticationOptions,
verifyAuthenticationResponse,
} from '@simplewebauthn/server';
app.post('/login/options', async (req, res) => {
const options = await generateAuthenticationOptions({
rpID,
// 特定ユーザーに絞る場合のみ allowCredentials を渡す
});
req.session.currentChallenge = options.challenge;
res.json(options);
});
フロント側はstartAuthentication()を呼ぶだけです。
// browser: 認証
import { startAuthentication } from '@simplewebauthn/browser';
async function loginWithPasskey() {
const optionsJSON = await fetch('/login/options', {
method: 'POST',
}).then((r) => r.json());
const authResp = await startAuthentication({ optionsJSON });
const result = await fetch('/login/verify', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(authResp),
}).then((r) => r.json());
if (result.verified) location.href = '/dashboard';
}
サーバでは、クレデンシャルIDで保存済みパスキーを引き、verifyAuthenticationResponse()に公開鍵・sign countを渡して署名を検証します。検証が通ったら、返ってきた新しいsign count(newCounter)でDBを更新します。この更新を怠るとクローン検知が働かないため、忘れずに行います。
// server: 認証レスポンスの検証
app.post('/login/verify', async (req, res) => {
const passkey = await getPasskeyById(req.body.id); // クレデンシャルIDで引く
if (!passkey) {
return res.status(400).json({ error: '未登録のパスキーです' });
}
const verification = await verifyAuthenticationResponse({
response: req.body,
expectedChallenge: req.session.currentChallenge,
expectedOrigin: origin,
expectedRPID: rpID,
credential: {
id: passkey.id,
publicKey: passkey.publicKey,
counter: passkey.counter,
transports: passkey.transports,
},
});
if (!verification.verified) {
return res.status(400).json({ error: '認証に失敗しました' });
}
// sign count を更新(クローン検知)
await updateCounter(passkey.id, verification.authenticationInfo.newCounter);
req.session.currentChallenge = undefined;
req.session.userId = passkey.userId; // ログイン成立
res.json({ verified: true });
});
challengeはサーバが生成し、リプレイ攻撃を防ぐために十分なランダム性を持たせ、使い捨てにします。W3C仕様は次のように定めています。
In order to prevent replay attacks, the challenges MUST contain enough entropy to make guessing them infeasible. Challenges SHOULD therefore be at least 16 bytes long.
出典:Web Authentication – Level 3, §13.4.3 Cryptographic Challenges|W3C(https://www.w3.org/TR/webauthn-3/#sctn-cryptographic-challenges)
保存すべきデータとDB設計
ここでは、登録時に何をDBへ保存し、それぞれが認証時の検証でどう使われるかを整理します。カラムを列挙するだけでは、後の実装で「これは何のために持っているのか」が分からなくなりがちなので、用途とセットで押さえます。
SimpleWebAuthnは、登録検証後に最低限保存すべきデータとして次の型を示しています。
type Passkey = {
id: Base64URLString; // クレデンシャルID(認証時に引くキー)
publicKey: Uint8Array; // 公開鍵(署名検証に使う)
user: UserModel; // 紐付けるユーザー
webauthnUserID: Base64URLString;
counter: number; // sign count(クローン検知)
deviceType: CredentialDeviceType; // singleDevice / multiDevice
backedUp: boolean; // 同期パスキーかどうか
transports?: AuthenticatorTransportFuture[];
};
このうちuserとwebauthnUserIDは、自社のユーザー(下表のuser_id)とWebAuthn上のユーザーハンドル(登録オプションのuser.id。省略時は自動生成されoptions.user.idに入る値を保存)に対応します。下表はクレデンシャル自体のカラムに絞った例です。
これをリレーショナルDBのテーブルに落とすと、次のような構成になります。ユーザー1人が複数のパスキー(別端末・別ブラウザ)を持てるよう、ユーザーとは1対多で設計します。
| カラム | 型の例 | 役割(検証でどう使うか) |
|---|---|---|
| id | text(Base64URL) | クレデンシャルID。認証時にどのパスキーかを特定する主キー |
| public_key | bytea | 公開鍵。認証時の署名検証に使う |
| counter | bigint | sign count。認証ごとに更新し、クローン検知に使う |
| transports | text[] | 認証器との通信手段。認証時のallowCredentialsに載せて再提示する |
| device_type | text | singleDevice / multiDevice(同期可否の目安) |
| backed_up | boolean | クラウド同期されるパスキーかどうか |
| webauthn_user_id | text(Base64URL) | WebAuthn上のユーザーハンドル(登録オプションのuser.id) |
| user_id | FK | ユーザーへの紐付け(1ユーザー対多パスキー) |
公開鍵は認証時の署名検証に使い、sign countは認証のたびに更新して認証器の複製を検知するためのものです。W3C仕様も、sign countの目的をクローン検知の補助と説明しています。
The signature counter’s purpose is to aid Relying Parties in detecting cloned authenticators.
出典:Web Authentication – Level 3, §6.1.1 Signature Counter Considerations|W3C(https://www.w3.org/TR/webauthn-3/#sctn-sign-counter)
device_typeとbacked_upは、認証器データのフラグ(Backup Eligibility/Backup State)に由来します。同期パスキー(クラウド経由で複数端末に存在するパスキー)かどうかの判別に使え、リカバリや端末追加の設計を考えるときの手がかりになります。
自前実装・ライブラリ・認証基盤の判断材料
ここまでで、パスキー実装で自分が書く範囲の全体量が見えてきました。それを踏まえて、実装方式を「自前(素のWebAuthn)」「ライブラリ」「認証基盤サービス(IDaaSやCIAM=顧客のID・アクセス管理を担うクラウド基盤)」の3つで比較します。どこまでを自作し、どこを任せるかで、開発・保守の負担とカスタマイズの自由度が変わります。
| 方式 | 自分で実装する範囲 | メリットと注意点 | 向くケース |
|---|---|---|---|
| 自前(素のWebAuthn API) | attestationObject・clientDataJSONの解析、署名検証、challenge管理、公開鍵の保管をすべて自作 | 外部依存ゼロで挙動を完全に把握できる。一方でCBOR/COSE(バイナリ符号化・公開鍵の形式)の解析や署名検証を誤ると脆弱になり、保守負担が大きい | 学習目的、特殊要件、外部依存を極力避けたい場合 |
| ライブラリ(SimpleWebAuthn等) | オプション生成・レスポンス検証はライブラリに任せ、DB保存・UI・リカバリは自作 | 検証の難所を肩代わりしつつ自社UI/DBを保てる。ライブラリのバージョン追従は必要 | 自社UIで作り込みたい、実装を自分でコントロールしたい場合 |
| 認証基盤(IDaaS・CIAM) | ホスト型UIやSDKに任せ、自作範囲は最小 | WebAuthnの低レベル処理・UI・リカバリまで肩代わり。料金やベンダーロックイン、カスタマイズの制約は考慮が必要 | 最短で導入したい、MFAやSSOを含む認証全体を任せたい場合 |
自前実装は自由度が高い反面、attestationの解析や署名検証を正しく実装する責任をすべて負います。ライブラリはその難所を引き受けつつ画面やデータ設計の自由を残し、認証基盤サービスは登録・認証の導線ごと任せられます。自社のリソースと要件に合わせて選ぶのが現実的です。記事後半では、実装を肩代わりする代表的なサービスを紹介します。
パスキー実装でつまずきやすいポイント
ここでは、実装で実際に詰まりやすい点を、症状ごとに引けるかたちで整理します。多くはRP IDやchallengeの扱い、conditional UIの設定に集約されます。
RP IDとoriginの設定
RP IDは、そのパスキーがどのドメインで使えるかを決める識別子で、デフォルトではフロントのoriginの実効ドメインになります。RP IDとoriginがかみ合っていないと登録・認証が失敗するため、実装で最初につまずきやすい点です。W3C仕様は次のように定義しています。
By default, the RP ID for a WebAuthn operation is set to the caller’s origin’s effective domain. This default MAY be overridden by the caller, as long as the caller-specified RP ID value is a registrable domain suffix of [or equal to] the caller’s origin’s effective domain.
出典:Web Authentication – Level 3, RP ID|W3C(https://www.w3.org/TR/webauthn-3/#rp-id)
RP IDにはポートやスキームを含めず、ドメインのみを指定します。login.example.comで動かしつつexample.comでも共通に使いたい場合は、登録可能なドメインの範囲でRP IDを親ドメインに設定します。サブドメインをまたぐ設計では、この範囲を踏まえた設定が必要です。
challengeのサーバ保持と使い捨て
challengeはサーバで生成し、検証まで一時保存し、使ったら破棄します。フロントで生成したり使い回したりすると、リプレイ攻撃に対する防御が働きません。セッションやサーバ側ストアにひも付けて保持し、検証後に必ず消すのが基本です。
conditional UI(autofillからのログイン)
ログイン画面のフォームに、パスワードのように候補としてパスキーを提示するのがconditional UIです。SimpleWebAuthnではstartAuthentication({ optionsJSON, useBrowserAutofill: true })で有効化し、入力欄に<input autocomplete="webauthn">を用意します。
仕様側ではisConditionalMediationAvailable()で利用可否を確認できます。ユーザー名を先に聞かずにログインさせたい場合に有効です。
excludeCredentialsによる重複登録の防止
同じユーザーが同じ認証器に二重でパスキーを作らないよう、登録オプションのexcludeCredentialsに登録済みクレデンシャルを渡します。W3C仕様も、既存クレデンシャルを列挙して重複作成を防ぐ用途と説明しています。
This ensures that the new credential is not created on an authenticator that already contains a credential mapped to this user account.
出典:Web Authentication – Level 3, excludeCredentials|W3C(https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialcreationoptions-excludecredentials)
ユーザー検証(userVerification)の必須・任意
生体認証やPINの要求は、オプションのuserVerificationでrequired/preferred/discouragedを指定します。ユーザー検証は認証器のローカルで行われ、サーバには生体情報そのものではなく、「検証したか」を示すUV(User Verification)フラグが伝わります。必須にするか任意にするかは、要件に応じた選択が必要です。
複数デバイスとリカバリ
ユーザーが複数の端末を使う場合、端末ごとにパスキーを追加登録できる導線が要ります。また、端末を紛失したときのために、別の認証手段(メール確認など)でのリカバリ経路を用意しておく必要があります。同期パスキーはクラウド経由で複数端末に広がりますが、すべての環境で同期されるわけではないため、リカバリ設計は省略できません。
パスキー実装の前提・動作条件
実装に入る前に、動かすための前提条件を確認しておきます。これを外すと、コードが正しくてもパスキーが動作しません。
HTTPS必須(セキュアコンテキスト)
WebAuthn APIはセキュアコンテキストでのみ利用できます。実運用ではHTTPSが必須で、TLSがエラーなく確立している必要があります。W3C仕様も次のように定めています。
Since this is an integral part of the WebAuthn security model, user agents only expose this API to callers in secure contexts.
出典:Web Authentication – Level 3|W3C(https://www.w3.org/TR/webauthn-3/)
開発時はlocalhostがセキュアコンテキストとして扱われるため、HTTPでも動作します。本番はHTTPS、開発はlocalhostで進めるのが基本です。
対応ブラウザ・OSの下限
同期パスキーが標準で使える主なバージョンの目安は、iOS/iPadOS 16以上、macOS 13(Ventura)以上、Android 9以上です。ブラウザではSafari 16.1以上(iOS/macOS)で、Chrome・Edge・Firefoxも近年の版で対応しています。これらは端末が素の状態で使える能力の目安で、実際の対応状況は更新されるため、実装前に最新情報を確認してください。
出典・参考資料(1件)
既存のパスワード・SMSログインとの共存
すでにパスワードやSMS認証を運用している場合は、パスキーを「追加の認証手段」として登録導線に置き、既存ログインを残したまま段階的に移行するのが現実的です。全ユーザーが一斉にパスキーへ移れるわけではないため、当面は複数の認証手段を併存させる設計にします。
パスキーを既存のパスワードやSMS認証と組み合わせ、多要素認証として運用する設計まで踏み込みたい場合は、認証方式ごとの特徴や併用時の選び方を整理した以下の比較記事も参考になります。
多要素認証(MFA)システムおすすめ17選を徹底比較|認証方式・料金・タイプ別の選び方
ID とパスワードだけのログインは、もはや「鍵をかけていない」のと同じ状態に近づいています。攻撃者は流出したパスワードのリストを使い回し、本物そっくりの偽サイトで認証情報を抜き取り、正規利用者になりすまして社内システムやクラウドサービスへ侵…
パスキー実装の限界と注意点
最後に、パスキーを過信しないための注意点を整理します。パスキーはフィッシングに強い認証ですが、「絶対に破られない」わけではありません。FIDO Allianceも、パスキーの利点を「フィッシング耐性(phishing-resistant)」と表現しており、ハッキング不可能とは述べていません。
出典・参考資料(1件)
sign countによるクローン検知も万能ではありません。W3C仕様は、sign countを実装しない認証器も存在すると述べており、その場合はcounterが常に0のまま返るため検知が働きません。counterはあくまで補助的な仕組みと捉えるのが適切です。
また、リカバリと非対応環境の設計は避けて通れません。端末の紛失・機種変更、同期に対応しない環境、旧いブラウザなどを考えると、パスキーだけで完結させるより、別の認証手段を併用しフォールバックを用意する方が安全です。完璧を目指すほどリカバリや非対応環境への対応コストは大きくなるため、どこまで作り込むかは自社の要件と照らした判断が必要です。
実装を肩代わりする認証基盤・SDK
前節で見た自前実装の負担を肩代わりするのが、パスキー対応のライブラリや認証基盤サービスです。前節の3分類(自前/ライブラリ/認証基盤)を提供形態で具体化すると、自前実装を助けるライブラリ、OSS認証サーバ、既存基盤を温存するパスキー専業、フルスタックのCIAMという4つの実装ルートに分けられます。
まずは主要なサービスを比較表で俯瞰します。
| 比較項目 | Auth0 | Amazon Cognito | Microsoft Entra External ID | Keycloak | Uni-ID Libra | SimpleWebAuthn | Corbado |
|---|---|---|---|---|---|---|---|
| 実装ルート | フルスタックCIAM | フルスタックCIAM(AWS基盤) | フルスタックCIAM(Microsoft基盤) | OSS認証サーバ(セルフホスト) | 国産CIAM(既存基盤へのパスキー追加も可) | 自前実装のライブラリ | パスキー専業(既存基盤を温存) |
| パスキー対応の提供形態 | ホスト型UI(Universal Login)+SDK | ホスト型UI(Managed Login)+SDK | ホスト型ユーザーフロー | ホスト型ログイン画面(セルフホストの認証サーバ) | CIAMバックエンドで対応(別製品「Uni-ID Libra FIDO」で既存基盤にパスキーのみ追加も可) | ライブラリ(ブラウザ用・サーバ用の2パッケージ) | 既存ログインの前段に差し込むマネージドのパスキー層+観測層 |
| 自前で実装が必要な範囲 | WebAuthnの低レベル処理・UI・リカバリまで肩代わりし、自作は最小(作り込む場合はSDK併用) | Managed LoginがWebAuthnの低レベル処理・UIを肩代わり。自作は最小(自社UIに組み込むならUSER_AUTHフローや公式サンプルを利用) | ホスト型フローに組み込むだけで低レベル処理は不要(カスタムポリシー不要の簡素化設計) | サーバ構築・DB・SSL・アップグレード・脆弱性対応を自社運用(パスキー導線とUIは製品が提供) | 顧客ID統合・認証基盤として導入。要件定義からNRIセキュアのコンサル支援を受けられ、既存基盤にパスキーだけを足す構成も選べる | UI・DB保存・セッション・リカバリ・複数デバイス管理まで自作(オプション生成と署名検証はライブラリが肩代わり) | 会員基盤・ユーザー管理は既存のまま。パスキーの提供・最適化を委任し、認証基盤の移行は不要 |
| 主な制約・注意点 | New Universal Loginが前提。料金はMAU課金・米ドル建てで、日本円建ては非公開 | パスキーはEssentials・Plusプランのみ(Liteは非対応)。料金はMAU課金・米ドル建てで、AWS基盤前提の設計 | パスキーはサインイン・MFAのみ対応で、サインアップ・パスワードリセットは非対応。外部テナントはM365 SSO非対応など機能制約あり | 公式マネージド提供なし。運用に専門知識が要る。パスキーはWebAuthnパスワードレスポリシーで有効化(既定は無効) | 料金は完全非公開で個別見積もり。導入事例は大企業中心。オンプレ/クラウドを選択可 | ホスト型UI・ユーザーDBは持たず周辺実装は全て自社。商用サポートなし(英語ドキュメント中心) | 汎用CIAMではなく会員基盤は別途必要。料金・無料枠は非公開。日本法人・日本語専任サポートは未確認 |
| 料金の目安 | 無料枠あり(MAU 25,000まで、パスキーは全プラン追加料金なし)。有料はEssentials $35〜ほか | 無料枠あり(Lite・Essentials 月10,000MAUまで)。パスキー対応のEssentialsは超過分$0.015/MAU、Plus $0.020/MAU | 無料枠50,000 MAUまで。超過分・アドオンは要問い合わせ(米ドル基準) | ソフトウェアは無料(Apache-2.0)。インフラ・運用人件費は自社負担 | 要問い合わせ(料金非公開。オンプレ/クラウドや機能要件で個別見積もり) | 無料(MIT/OSS。インフラ運用費は自社負担) | 要問い合わせ(MAUまたはログインボリューム課金、金額は非公開) |
| 向くケース | ホスト型UIでパスキーを最短導入し、MFA・SSOを含む認証全体を任せたいケース | すでにAWS上でシステムを構築し、認証基盤をAWSに寄せてパスキーを追加したいケース | Microsoft 365/Azure基盤の組織が、外部向けログインにパスキーを組み込むケース | ライセンス費ゼロで認証基盤を自社管理し、データの持ち場を自社に置きたいチーム | 国産ベンダーの日本語サポートを重視し、既存のID基盤にパスキーを追加したい企業 | 自社UI・DBを保ちつつ、低レベル処理だけ任せたい開発チーム | 認証基盤を替えず、既存サービスにパスキーだけを追加したいケース |
| 詳細情報 | サービス詳細を見る | サービス詳細を見る | サービス詳細を見る | サービス詳細を見る | サービス詳細を見る | 公式サイト | 公式サイト |
※料金・対応状況は2026年9月時点の各社公式情報にもとづく目安です。最新の内容は各公式サイトでご確認ください。
ここからは、各サービスがパスキー実装をどこまで肩代わりするかを個別に見ていきます。
1. Auth0(Okta, Inc.)

SDKと管理ダッシュボードでログイン機能を実装できるフルスタックの認証・認可プラットフォームが、Auth0です。パスキーはFIDO2標準に基づき、New Universal Login(新しいユニバーサルログイン)を前提に、ダッシュボードのDatabase Connectionで有効化できます。
ホスト型のログイン画面がWebAuthnの低レベル処理とUIを肩代わりするため、自社でフローを組む負担を抑えて最短で導入したいケースに向きます。
2. Amazon Cognito(Amazon Web Services, Inc.)

AWSが提供するマネージド型のCIAMがAmazon Cognitoです。会員登録・サインイン基盤であるUser Poolsを中心に、パスキー(WebAuthn)にネイティブ対応しており、Managed Loginのホスト型画面がパスキーの導線を肩代わりします。すでにAWS上でシステムを構築している場合、認証基盤をAWSに寄せてパスキーを追加したいケースと相性が良いのが特徴です。
3. Microsoft Entra External ID(Microsoft Corporation)

Microsoftが外部ユーザー(顧客・取引先)向けに提供するID・アクセス管理サービスが、Microsoft Entra External IDです。パスキーはサインインとMFAで利用できますが、サインアップやパスワードリセットでは利用できない点に注意が必要です。Microsoft 365やAzureを基盤にしている組織が、外部向けログインにパスキーを組み込みたいケースで検討対象になります。
4. PingOne for Customers(Ping Identity)

PingOne for Customersは、エンタープライズ領域を主力とするPing IdentityのクラウドCIAMです。FIDO2認定を受けた認証サーバを備え、パスキーを含む多要素認証を大規模なユーザー基盤で運用できます。厳格なセキュリティ要件やガバナンスが求められる大企業が、認証全体を統合しつつパスキーを導入したい場合に向きます。
5. Keycloak(Keycloakプロジェクト/Red Hat)

オープンソースの認証・認可サーバであるKeycloakは、自社サーバやコンテナ基盤にセルフホストして使い、WebAuthnのパスワードレスポリシーを有効化することでパスキーに対応できます。ライセンス費用をかけずに認証基盤を自社で管理したい、データの持ち場を自社に置きたいといった要件を持つ開発チームに適しています。
6. Uni-ID Libra(NRIセキュアテクノロジーズ株式会社)

Uni-ID Libraは、NRIセキュアテクノロジーズが提供する国産のCIAMです。FIDO認定に対応し、既存のID基盤にパスキーを追加できる構成を備えています。日本語でのサポートや国内の金融水準を意識した運用実績が強みで、国産ベンダーによる支援を重視する企業に適しています。
7. SimpleWebAuthn(オープンソースプロジェクト)

本記事のコード例でも使ったSimpleWebAuthnは、WebAuthnの実装を助けるオープンソースのライブラリです。ブラウザ用とサーバ用のパッケージがあり、オプション生成とレスポンス検証という難所を肩代わりします。UI・DB保存・リカバリは自作する前提なので、認証基盤サービスに丸ごと任せるのではなく、自社の画面やデータ設計を保ちながらパスキーを実装したい開発チームに向きます。
8. Corbado(Corbado GmbH)

パスキーに特化したドイツ発のサービスが、Corbadoです。既存のログイン基盤を残したままパスキーを追加できる構成を得意とし、パスキー導入に伴う実装や運用のノウハウを提供しています。認証基盤を全面的に置き換えるのではなく、いまのシステムにパスキーだけを上乗せしたいケースで有力な選択肢になります。
9. Hanko(Hanko GmbH)

同じくオープンソースで、パスキーファーストの設計を掲げるのがドイツのHankoです。自己ホストするか、マネージドのクラウド版を使うか、Passkey APIだけを組み込むかを選べる柔軟さが特徴です。オープンソースを軸にパスキー中心の認証を構築したい、あるいは小規模から試したい開発チームに向きます。
10. Stytch(Stytch, Inc.)

開発者向けのAPIとSDKで認証を組み込めるStytchも、パスキーに対応しています。WebAuthnのエンドポイントとSDKを提供し、自社アプリへ柔軟に組み込めます。2025年10月にはTwilioによる買収が発表されており、買収後の提供体制やブランドは導入前に最新情報を確認することをおすすめします。
11. Descope(Descope, Inc.)

ノーコードのフロー設計が特徴のDescopeは、画面上でパスキーなどの認証フローを組み立てられます。管理画面でログインフローを作り、SDKでアプリに組み込む形のため、認証フローを画面上で調整したいケースに向きます。パスキーの登録・認証の導線を、コードを書きすぎずに構築したいチームに適しています。
12. Supabase Auth(Supabase, Inc.)

Supabase Authは、BaaS(Backend as a Service)であるSupabaseに組み込まれた認証機能です。Supabaseでバックエンドを構築しているプロジェクトなら、同じ基盤で認証を扱えます。
ただし、パスキー対応は執筆時点でexperimental(実験的)な位置づけで、正式提供ではなくAPIの変更やオプトインが前提となるため、本番運用では正式提供のサービスと比べて慎重に検討してください。
まとめ
パスキーの実装は、登録と認証の2つのセレモニーを、ブラウザ・サーバ・認証器の三者でやり取りする流れとして捉えると全体像が掴めます。フロントはnavigator.credentials(ライブラリならstartRegistration()/startAuthentication())を呼び、サーバはchallenge・origin・署名を検証し、公開鍵とsign countを保存・更新します。
RP IDとoriginの整合、challengeの使い捨て、HTTPS必須といった前提を押さえれば、大きくつまずくことは避けられます。
実装方式は、自前・ライブラリ・認証基盤の3通りから、自社のリソースと要件で選べます。署名検証やUI・リカバリまで自作する負担を避けたいなら、パスキー対応の認証基盤・SDKに任せる選択肢が有力です。各サービスの資料を取り寄せて、自社の要件に合うものを比較検討してみてください。
よくある質問(FAQ)
Q. パスキーとは何ですか?
A. パスキーとは、公開鍵暗号を使ってパスワードの代わりにログインを行う認証情報(クレデンシャル)です。登録時に認証器(スマートフォンやPCの生体認証、セキュリティキー)が秘密鍵と公開鍵のペアを作り、秘密鍵は端末内に留め、公開鍵だけをサービス側のサーバに預けます。ログイン時は端末側で生体認証やPINによりユーザーを確認し、秘密鍵でサーバからの乱数(challenge)に署名して本人性を証明します。
技術的にはFIDO2/WebAuthnという標準規格の上に成り立っています。
Q. パスキーの実装に出てくるWebAuthnとFIDO2は何が違いますか?
A. WebAuthnはブラウザとサーバがやり取りするためのW3C標準API、FIDO2はそのWebAuthnと認証器側の規格(CTAP)をまとめたFIDOアライアンスの枠組みで、パスキーはこれらの技術で作られる認証情報の呼び名です。
実装者が直接触れるのは主にブラウザのnavigator.credentials(WebAuthn API)とサーバ側の検証処理で、FIDO2はその背後にある標準の総称と捉えると整理しやすくなります。「パスキーを実装する」とは、実質的にWebAuthnを組み込むことを指します。
Q. パスキーは自前実装・ライブラリ・認証基盤のどれで実装すべきですか?
A. パスキーは、署名検証やUI・リカバリまで自作する余力があるかで選ぶのが現実的で、検証の難所だけ任せたいならSimpleWebAuthn等のライブラリ、認証全体を任せて最短で導入したいなら認証基盤サービス(IDaaS・CIAM)が向きます。
素のWebAuthnを自前で書く方式は挙動を完全に把握できる反面、CBOR/COSEの解析や署名検証を誤ると脆弱になり保守負担も大きいため、特殊要件や学習目的以外では慎重に判断するのが無難です。
Q. パスキーの実装でサーバに秘密鍵は保存されますか?
A. いいえ、パスキーでは秘密鍵はサーバに保存されず、サーバが保存するのは公開鍵・クレデンシャルID・sign count(カウンター)などです。秘密鍵は認証器の中に留まりネットワークには出ないため、サーバ側のデータが万一漏れても、それだけではなりすましログインはできません。保存した公開鍵は認証時の署名検証に、sign countは認証器の複製を検知する補助に使います。
Q. パスキーのRP IDとoriginには何を設定すればよいですか?
A. RP IDにはパスキーを有効にしたいドメイン(例: example.com)を、originにはフロントのURL(例: https://example.com)を設定し、RP IDはoriginの実効ドメインと一致させるか、その登録可能な親ドメインにします。RP IDにはポートやスキームを含めません。
両者がかみ合っていないと登録・認証が失敗するため、複数のサブドメインで共通利用したい場合はRP IDを親ドメインにそろえる設計にします。
Q. パスキーはlocalhostやHTTP環境でも実装・テストできますか?
A. 開発時のlocalhostはセキュアコンテキストとして扱われるためHTTPでもパスキーを動かせますが、本番環境ではHTTPSが必須です。WebAuthn APIはセキュアコンテキストでのみ利用できる仕様のため、独自ドメインのステージング環境などで検証する場合はTLSを有効にする必要があります。ローカルで動いても本番でHTTPのままだと動作しない点に注意します。
Q. パスキー実装でconditional UI(オートフィル)は必須ですか?
A. conditional UI(オートフィル)は必須ではありませんが、ユーザー名を先に入力させずにログイン欄からパスキー候補を選ばせたい場合に有効な仕組みです。SimpleWebAuthnでは認証時にuseBrowserAutofill: trueを指定し、入力欄にautocomplete="webauthn"を付けて有効化します。
ブラウザが対応しているかはisConditionalMediationAvailable()で判定できます。まずは通常のパスキー認証を実装し、UXを高める段階で導入しても問題ありません。
Q. ユーザーが端末を紛失したとき、パスキーはどうやってリカバリしますか?
A. パスキーのリカバリは、クラウド同期された別端末からのサインイン、複数パスキーの事前登録、またはメール確認など別の認証手段によるフォールバックで行うのが基本です。同期パスキーは同じアカウント(Apple・Googleなど)に紐づく別端末へ広がりますが、すべての環境で同期されるわけではないため、端末の紛失・機種変更に備えたリカバリ経路を実装時に必ず用意しておく必要があります。
Q. パスキーはどのブラウザ・OSで使えますか?
A. 同期パスキーは、iOS/iPadOS 16以上・macOS 13以上・Android 9以上と、Safari 16.1以上やChrome・Edge・Firefoxの近年の版で利用できます。これらは端末が素の状態で使える能力の目安で、対応状況は継続的に更新されます。実装前には各ブラウザ・OSの最新の公式情報を確認し、非対応環境のユーザーへのフォールバックも合わせて設計してください。
Q. パスキーを導入したら既存のパスワードログインは廃止すべきですか?
A. すぐに廃止せず、当面はパスキーを「追加の認証手段」として既存のパスワード・SMS認証と併存させるのが現実的です。全ユーザーが一斉にパスキーへ移れるわけではなく、非対応環境のユーザーも残るため、パスキーを登録導線に追加しつつ、段階的に移行しながらフォールバックを維持する設計が安全です。利用率が十分に高まった段階で、パスワード廃止を検討します。
Q. パスキーは本当に安全ですか?ハッキングされる可能性はありますか?
A. パスキーは秘密鍵が端末外に出ずドメインに紐付くためフィッシングに強い認証ですが、「絶対に破られない」わけではありません。FIDOアライアンスもパスキーをフィッシング耐性(phishing-resistant)と表現しており、ハッキング不可能とはしていません。
端末の紛失やリカバリ経路の悪用、非対応環境といった現実の穴は残るため、過信せず、別の認証手段の併用やリカバリ設計まで含めて全体の安全性を確保することが大切です。
法人向けサービスを課題別に探すならMCB FinTechカタログ
MCB FinTechカタログでは、決済・金融・バックオフィス領域の法人向けサービスを課題別に探し、気になるサービスの資料をまとめて請求できます。生体認証・パスキー認証SDKのほかの課題も、あわせてご確認ください。
MCB FinTechカタログに掲載しませんか?
MCB FinTechカタログでは、掲載企業様を募集しています。マネックスグループの金融実務ノウハウを活かした独自の評価軸と検索設計により、導入検討者が最適なサービスを効率的に発見できる法人向け比較プラットフォームです。掲載後は管理画面から料金表や導入事例を随時更新でき、常に最新の情報を訴求可能。まずは下記フォームより、お気軽にお問い合わせください。

マネックス証券 フィナンシャル・インテリジェンス部 暗号資産アナリスト
松嶋真倫
監修の範囲は記事の一般的な解説部分であり、比較表および各製品・サービスの紹介内容は監修の対象外です。
















