メインコンテンツまでスキップ

FIDO2 認証フローとインターフェース詳細


概要​

このドキュメントは、W3C WebAuthn Level 2仕様のFigure 2 Authentication Flowに基づいて、FIDO2認証フローの各インターフェース(①〜⑥)とパラメータを詳細に解説します。

情報源: W3C WebAuthn Level 2 - Figure 2 Authentication Flow

このドキュメントで学べること:

  • Relying Party ServerとRP JavaScript間の通信(①、⑤)
  • BrowserとAuthenticator間のWebAuthn API(②、④)
  • Authenticator内部処理(③)
  • RP Serverでの検証処理(⑥)
  • 各インターフェースの標準化状況

アーキテクチャ図​

┌─────────────────────────────────────────────────────────┐
│ Relying Party Server │
│ ⑥ server │
│ validation │
└───────────────┬──────────────────▲──────────────────────┘
│ │
① challenge ⑤ clientDataJSON,
authenticatorData,
signature
│ │
┌───────────────▼──────────────────┴──────────────────────┐
│ RP JavaScript Application │
│ (Webブラウザー内で実行) │
├─────────────────────────────────────────────────────────┤
│ Browser (User Agent) │
│ WebAuthn API実装 │
└───────────────┬──────────────────▲──────────────────────┘
│ │
② relying party id, ④ authenticatorData,
clientDataHash signature
│ │
┌───────────────▼──────────────────┴──────────────────────┐
│ Authenticator │
│ ③ user verification, │
│ create assertion │
└─────────────────────────────────────────────────────────┘

W3C WebAuthn仕様の標準化範囲​

W3C WebAuthn仕様は、全てのインターフェースを標準化しているわけではありません。標準化の範囲を理解することが重要です。

✅ W3C WebAuthn仕様が標準化しているもの​

項目説明標準化の目的
JavaScript API(②、④)navigator.credentials.get() のインターフェースBrowserの動作を統一(相互運用性)
データ構造authenticatorData、signature、clientDataJSON の構造RP ↔ Browser ↔ Authenticator間のデータ交換を統一
検証手順(⑥)RPが実行すべき検証ステップ(Section 7.2)セキュリティ要件の明確化
型定義PublicKeyCredentialRequestOptions 等の TypeScript/IDL 定義API仕様の明確化

標準化の範囲:

Browser(User Agent)の実装 = 完全に標準化
↓
・navigator.credentials.get() の動作
・authenticatorData の生成方法
・clientDataJSON の構造
・Authenticator との通信プロトコル(CTAP)

❌ W3C WebAuthn仕様が標準化していないもの​

項目説明理由
RP Server ↔ RP JavaScript間の通信(①、⑤)HTTPエンドポイント、リクエスト/レスポンス構造各RPが独自のバックエンドAPI設計を採用できるようにするため
パラメータ名username / user_name / email 等RP内部の設計自由度を保つため
エンドポイントURL/fido2-authentication-challenge 等RESTful設計やURL設計はRP次第
認証フロー全体OAuth 2.0連携、セッション管理等RPごとに認証アーキテクチャが異なるため

非標準化の範囲:

RP Server ↔ RP JavaScript の通信 = 標準化なし
↓
・HTTPエンドポイントURL
・リクエストのJSON構造
・レスポンスのJSON構造
・パラメータ名
・エラーレスポンス形式

📖 W3C仕様の明確な記述​

W3C WebAuthn Level 2 - Section 1.2 Conformance: "This specification does not define a server-side API; it only defines the client-side API."

日本語訳: "この仕様はサーバー側APIを定義していません。クライアント側APIのみを定義します。"

これの意味:

  • ✅ Browserの動作(JavaScript API、データ構造)は完全に標準化
  • ❌ RPのバックエンドAPI(①、⑤)は各実装の自由

なぜこのような設計なのか?​

観点理由
相互運用性Browserの動作を統一すれば、どのRPでも同じJavaScript APIで実装可能
柔軟性RPごとに異なるバックエンドアーキテクチャ(Node.js、Java、Python等)に対応
進化可能性RPのバックエンドは自由に進化できる(新機能追加、パフォーマンス改善等)
責任分離W3CはBrowser実装を標準化し、RPはセキュリティ要件(検証手順)のみ遵守

実例: idp-server、Google、GitHub、Microsoftは全て異なるバックエンドAPI設計ですが、全て同じWebAuthn APIで動作します。


① RP Server → RP JavaScript: チャレンジ取得​

通信: HTTP(各実装が自由に設計)

一般的なリクエスト​

POST /fido2-authentication-challenge
Content-Type: application/json

{
"username": "user@example.com"
}

一般的なレスポンス​

{
"challenge": "Y2hhbGxlbmdl...",
"rpId": "example.com",
"allowCredentials": [
{
"type": "public-key",
"id": "credential_id_base64url",
"transports": ["internal"]
}
],
"timeout": 60000,
"userVerification": "preferred"
}

主要パラメータ​

パラメータ型説明例
challengeBase64URLランダムチャレンジ(32バイト以上推奨)"Y2hhbGxlbmdl..."
rpIdStringRPのドメイン名(省略時はcurrent origin)"example.com"
allowCredentialsArray許可するCredential IDリスト[{type, id, transports}]
timeoutNumberタイムアウト(ミリ秒)60000
userVerificationStringUser Verification要件"required" / "preferred" / "discouraged"

allowCredentialsの2つのパターン​

パターンallowCredentialsユーザー名入力用途
ユーザー名入力ありRPがCredential IDを指定必要2要素認証、既存システムとの統合
パスワードレス空配列 []不要パスワードレスログイン(Discoverable Credential必須)

詳細: basic-17: FIDO2・パスキー・Discoverable Credential

セキュリティ要件​

  • ✅ challengeは暗号学的に安全なランダム値(32バイト以上推奨)
  • ✅ サーバー側でチャレンジを一時保存(検証時に使用、1回のみ有効)
  • ✅ チャレンジの有効期限を設定(例: 2分)
  • ✅ allowCredentialsはユーザーに関連付けられたCredential IDのみ返す

② Browser → Authenticator: WebAuthn API呼び出し​

通信: WebAuthn API(W3C標準)

JavaScriptコード​

// ① で取得したレスポンスを変換
const publicKeyOptions = {
challenge: base64UrlToBuffer(serverResponse.challenge),
rpId: serverResponse.rpId,
allowCredentials: serverResponse.allowCredentials.map(cred => ({
type: cred.type,
id: base64UrlToBuffer(cred.id),
transports: cred.transports
})),
timeout: serverResponse.timeout,
userVerification: serverResponse.userVerification
};

// WebAuthn API呼び出し
const assertion = await navigator.credentials.get({
publicKey: publicKeyOptions
});

Browserから認証器へ渡されるデータ​

データ説明由来
rpIdRPのドメイン名サーバーから受領
allowCredentials許可するCredential IDリストサーバーから受領
clientDataHashclientDataJSONのSHA-256ハッシュBrowser内部で生成
userVerificationUser Verification要件サーバーから受領

clientDataJSONの内容​

{
"type": "webauthn.get",
"challenge": "Y2hhbGxlbmdl...",
"origin": "https://example.com",
"crossOrigin": false
}

重要: BrowserはclientDataJSONを自動生成し、そのSHA-256ハッシュを認証器に渡します。


③ Authenticator内部: 署名生成とAssertion作成​

認証器の処理 (FIDO CTAP仕様準拠):

1. Credentialの検索​

allowCredentials動作
Credential ID指定あり指定されたCredential IDに一致する秘密鍵を検索
空配列 []Discoverable Credential(内部に保存済み)から検索

Credential IDが見つからない場合: エラー返却(NotAllowedError)

2. ユーザー検証(User Verification)​

設定値動作
userVerification="required"生体認証またはPIN入力を必須とする
userVerification="preferred"可能なら検証、不可能ならスキップ
userVerification="discouraged"検証なし(タップのみ)

3. 署名生成​

  • 秘密鍵でAssertion(署名)を生成
  • 署名対象: authenticatorData || clientDataHash
  • signCountをインクリメント(クローン検出用)

④ Authenticator → Browser: Assertion Response返却​

認証器がBrowserに返すデータ:

// assertion.response の内容
{
authenticatorData: ArrayBuffer, // バイナリデータ
clientDataJSON: ArrayBuffer, // JSON文字列のバイナリ
signature: ArrayBuffer, // 署名データ
userHandle: ArrayBuffer // user.id(Discoverable Credentialの場合)
}

authenticatorData の構造(37バイト以上)​

フィールドサイズ説明
rpIdHash32バイトrpIdのSHA-256ハッシュ
flags1バイトUP(User Present), UV(User Verified), BE(Backup Eligibility), BS(Backup State)
signCount4バイト署名カウンター(クローン検出に使用)

flags(1バイト)の内訳​

ビット名称説明
bit 0UP (User Present)ユーザーがタップした(物理的存在確認)
bit 2UV (User Verified)生体認証またはPIN入力が完了
bit 3BE (Backup Eligibility)バックアップ可能(Level 3で追加)
bit 4BS (Backup State)バックアップ済み(Level 3で追加)

注意: 認証時はATフラグ(Attested Credential Data)は含まれません(登録時のみ)

主要パラメータ​

パラメータ説明用途
authenticatorDatarpIdHash、flags、signCountを含むバイナリ検証に使用
signature秘密鍵で生成された署名公開鍵で検証
clientDataJSONBrowserが生成したJSONチャレンジ検証に使用
userHandleuser.id(Discoverable Credentialの場合)ユーザー識別

⑤ RP JavaScript → RP Server: Assertion送信​

通信: HTTP(各実装が自由に設計)

一般的なリクエスト​

POST /fido2-authentication
Content-Type: application/json

{
"id": "credential_id_base64url",
"rawId": "credential_id_base64url",
"type": "public-key",
"response": {
"clientDataJSON": "eyJ0eXBlIjoid2ViYXV0aG4uZ2V0Ii...",
"authenticatorData": "SZYN5YgOjGh0NBcPZHZgW4...",
"signature": "MEUCIQDqV7Lzc...",
"userHandle": "dXNlcjEyMw"
}
}

JavaScriptコード例​

// ArrayBufferをBase64URLに変換
function bufferToBase64Url(buffer) {
const bytes = new Uint8Array(buffer);
let binary = '';
for (let i = 0; i < bytes.length; i++) {
binary += String.fromCharCode(bytes[i]);
}
return btoa(binary).replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
}

// Assertion送信
const response = await fetch('/fido2-authentication', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
id: assertion.id,
rawId: bufferToBase64Url(assertion.rawId),
type: assertion.type,
response: {
clientDataJSON: bufferToBase64Url(assertion.response.clientDataJSON),
authenticatorData: bufferToBase64Url(assertion.response.authenticatorData),
signature: bufferToBase64Url(assertion.response.signature),
userHandle: assertion.response.userHandle
? bufferToBase64Url(assertion.response.userHandle)
: null
}
})
});

主要パラメータ​

パラメータ型説明
idStringCredential ID(Base64URL)
rawIdStringCredential ID(Base64URL、idと同じ)
typeString常に "public-key"
response.clientDataJSONStringclientDataJSONのBase64URL
response.authenticatorDataStringauthenticatorDataのBase64URL
response.signatureString署名のBase64URL
response.userHandleStringuser.idのBase64URL(Discoverable Credentialの場合)

⑥ RP Server内部: サーバー側検証​

検証項目(W3C仕様 Section 7.2準拠):

1. Credential ID検証​

✅ Credential IDがデータベースに存在すること
✅ Credential IDとユーザーの関連付けが正しいこと
✅ 公開鍵をデータベースから取得

2. clientDataJSON検証​

✅ type が "webauthn.get" であること
✅ challenge が保存済みチャレンジと一致すること
✅ origin が許可リストに含まれること
✅ crossOrigin が false であること(Same Origin検証)

3. authenticatorData検証​

✅ authData.rpIdHash が rpId のSHA-256ハッシュと一致すること
✅ authData.flags.UP が 1(User Present)であること
✅ authData.flags.UV が要求通り(userVerification="required"の場合)

4. 署名検証​

✅ signature が公開鍵で検証できること
✅ 署名対象: authenticatorData || sha256(clientDataJSON)

署名検証の重要性:

  • 秘密鍵の所有証明
  • 認証器が正当であることの確認
  • 署名検証失敗 = 認証失敗

5. signCount検証(クローン検出)​

✅ 現在のsignCountが前回保存値より大きいこと
✅ signCountが0の場合は検証スキップ(一部認証器は非対応)
✅ signCountが減少している場合はクローンの可能性

クローン検出の重要性:

  • 認証器のクローン(不正コピー)を検出
  • signCountが減少 = セキュリティアラートを発行

レスポンス例​

HTTP/1.1 200 OK
Content-Type: application/json

{
"status": "success",
"user": {
"id": "user123",
"name": "user@example.com"
}
}

インターフェース標準化状況まとめ​

IF通信標準化状況備考
① RP Server → RP JavaScriptHTTP❌ 標準化なし各RP実装が自由に設計
② Browser → AuthenticatorWebAuthn API✅ W3C標準navigator.credentials.get()
③ Authenticator内部処理-✅ FIDO CTAP仕様署名生成、User Verification
④ Authenticator → BrowserWebAuthn API✅ W3C標準AuthenticatorAssertionResponse
⑤ RP JavaScript → RP ServerHTTP❌ 標準化なし各RP実装が自由に設計
⑥ RP Server内部検証-✅ W3C標準(検証手順)Section 7.2で手順規定

重要な結論:

  • W3C WebAuthn仕様は、②、④のクライアント側APIと⑥の検証手順のみ標準化
  • ①、⑤のRP ServerとRP JavaScript間の通信は標準化されていない
  • 各RPが独自のAPI設計(エンドポイント、パラメータ名、データ構造)を採用可能
  • idp-server、Google、GitHub等、各サービスでAPI設計が異なる

まとめ​

重要なポイント​

  1. 標準化の範囲

    • ✅ クライアント側API(②、④): W3C WebAuthn標準
    • ✅ 認証器処理(③): FIDO CTAP標準
    • ✅ 検証手順(⑥): W3C WebAuthn標準(Section 7.2)
    • ❌ RP ServerとRP JavaScript間の通信(①、⑤): 標準化なし
  2. セキュリティの要

    • challenge: 暗号学的に安全なランダム値(32バイト以上)
    • rpIdHash: フィッシング攻撃防止
    • origin: Same Origin検証
    • signature: 公開鍵で署名検証(秘密鍵の所有証明)
    • signCount: クローン検出
  3. データの流れ

    • サーバー → ブラウザー: challenge, allowCredentials, userVerification
    • ブラウザー → 認証器: rpId, allowCredentials, clientDataHash
    • 認証器 → ブラウザー: authenticatorData, signature, clientDataJSON
    • ブラウザー → サーバー: id, type, response
  4. 実装の自由度

    • ①、⑤のインターフェースはRP実装ごとに異なる
    • エンドポイント名、パラメータ名、HTTPメソッド等は自由
    • allowCredentialsのパターン(指定あり/空配列)でUXが変わる

参考リソース​

W3C WebAuthn Level 2仕様​

FIDO仕様​

関連ドキュメント​


このドキュメントは、W3C WebAuthn Level 2仕様に基づいて作成されています。