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

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の動作を統一(相互運用性)
データ構造authenticatorDatasignatureclientDataJSON の構造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 / emailRP内部の設計自由度を保つため
エンドポイントURL/fido2-authentication-challengeRESTful設計や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仕様に基づいて作成されています。