WebAuthn
Web Authentication APIの略称。Webブラウザとサーバー間でパスワードレス認証を実現するW3C標準のJavaScript API。FIDO2の一部を構成する。
概要
WebAuthnは、Webアプリケーションが公開鍵暗号を用いた強力な認証を実装できるようにするブラウザAPI。パスワードに依存せず、フィッシング耐性のある認証を実現する。
アーキテクチャ
Webサービス(Relying Party)
↕ HTTPS
Webブラウザ
├─ WebAuthn API(JavaScript)
└─ 認証器マネージャー
↕ CTAP2(USB/NFC/BLE)
認証器(Authenticator)
├─ プラットフォーム認証器(内蔵)
└─ 外部認証器(セキュリティキー等)
主要な概念
Relying Party(RP: 依拠当事者)
認証を受けるWebサービス。ドメイン名で識別される。
const rpID = "example.com";
const rpName = "Example Service";
Authenticator(認証器)
秘密鍵を生成・保管し、署名を行うデバイス。
- プラットフォーム認証器: デバイス内蔵(Touch ID、Windows Hello等)
- クロスプラットフォーム認証器: 外付け(YubiKey、スマホ等)
Credential(クレデンシャル)
公開鍵ペアと関連メタデータ。
{
id: "credential-id", // クレデンシャルID
type: "public-key", // 種別
publicKey: ArrayBuffer, // 公開鍵
authenticatorData: {...} // 認証器情報
}
認証フロー
1. 登録(Registration / Attestation)
新しいクレデンシャルを作成し、公開鍵をサーバーに登録する。
// サーバーからチャレンジを取得
const options = {
challenge: Uint8Array.from("random-challenge", c => c.charCodeAt(0)),
rp: {
name: "Example Corp",
id: "example.com"
},
user: {
id: Uint8Array.from("user-id-123", c => c.charCodeAt(0)),
name: "user@example.com",
displayName: "User Name"
},
pubKeyCredParams: [
{ type: "public-key", alg: -7 }, // ES256
{ type: "public-key", alg: -257 } // RS256
],
authenticatorSelection: {
authenticatorAttachment: "platform", // or "cross-platform"
userVerification: "required" // 生体認証/PIN必須
},
timeout: 60000,
attestation: "direct" // or "none", "indirect"
};
// 認証器で公開鍵ペアを生成
const credential = await navigator.credentials.create({
publicKey: options
});
// 公開鍵をサーバーに送信して登録
await fetch('/webauthn/register', {
method: 'POST',
body: JSON.stringify({
id: credential.id,
rawId: Array.from(new Uint8Array(credential.rawId)),
response: {
clientDataJSON: Array.from(new Uint8Array(credential.response.clientDataJSON)),
attestationObject: Array.from(new Uint8Array(credential.response.attestationObject))
},
type: credential.type
})
});
2. 認証(Authentication / Assertion)
既存のクレデンシャルを使ってログインする。
// サーバーからチャレンジを取得
const options = {
challenge: Uint8Array.from("random-challenge", c => c.charCodeAt(0)),
rpId: "example.com",
allowCredentials: [
{
type: "public-key",
id: Uint8Array.from(credentialId, c => c.charCodeAt(0))
}
],
userVerification: "required",
timeout: 60000
};
// 認証器で署名
const assertion = await navigator.credentials.get({
publicKey: options
});
// 署名をサーバーに送信して検証
await fetch('/webauthn/authenticate', {
method: 'POST',
body: JSON.stringify({
id: assertion.id,
rawId: Array.from(new Uint8Array(assertion.rawId)),
response: {
clientDataJSON: Array.from(new Uint8Array(assertion.response.clientDataJSON)),
authenticatorData: Array.from(new Uint8Array(assertion.response.authenticatorData)),
signature: Array.from(new Uint8Array(assertion.response.signature)),
userHandle: assertion.response.userHandle ? Array.from(new Uint8Array(assertion.response.userHandle)) : null
},
type: assertion.type
})
});
セキュリティ特性
フィッシング耐性
ドメインごとに異なる公開鍵ペアを生成するため、フィッシングサイトでは利用できない。
example.com用の鍵ペア → example.comでのみ有効
evil-example.com用の鍵ペア → 別の鍵(使い回し不可)
リプレイ攻撃耐性
チャレンジは毎回ランダムに生成され、署名に含まれるため、リプレイ攻撃が不可能。
チャレンジ1 → 署名1(チャレンジ1に対してのみ有効)
チャレンジ2 → 署名2(署名1は無効)
中間者攻撃耐性
TLS通信を前提とし、オリジン情報が署名に含まれる。
clientDataJSON
├─ type: "webauthn.get"
├─ challenge: "..."
├─ origin: "https://example.com" ← オリジン検証
└─ crossOrigin: false
ブラウザサポート
対応ブラウザ
| ブラウザ | バージョン | プラットフォーム認証器 |
|---|---|---|
| Chrome | 67+ | Windows Hello, Touch ID |
| Firefox | 60+ | Windows Hello |
| Safari | 13+ | Touch ID, Face ID |
| Edge | 18+ | Windows Hello |
機能検出
if (window.PublicKeyCredential) {
// WebAuthnサポートあり
// プラットフォーム認証器の有無を確認
const available = await PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable();
if (available) {
// Touch ID、Windows Hello等が利用可能
}
}
Attestation(構成証明)
認証器が本物であることを証明する仕組み。
Attestation Statement Format
- Packed: FIDO2標準形式
- TPM: Trusted Platform Module
- Android Key: Android端末
- Apple: Touch ID、Face ID
- None: 構成証明なし
Attestation Conveyance
attestation: "none" // 構成証明不要(プライバシー重視)
attestation: "indirect" // 匿名化された構成証明
attestation: "direct" // 完全な構成証明(デバイス識別可)
User Verification(ユーザー検証)
認証器がユーザー本人であることを確認する仕組み。
User Verification Methods
- 生体認証: 指紋、顔、虹彩
- PIN: 認証器に設定されたPIN
- なし: 所持認証のみ
User Verification Policy
userVerification: "required" // 必須
userVerification: "preferred" // 推奨(できれば)
userVerification: "discouraged" // 不要
Resident Key(Discoverable Credential)
ユーザーIDをサーバーに問い合わせずに認証できるクレデンシャル。
Non-Resident Key(従来)
1. ユーザーがメールアドレスを入力
2. サーバーがクレデンシャルIDを返却
3. 認証器がそのIDで署名
Resident Key
1. ユーザーが何も入力せず認証開始
2. 認証器内に保存されたクレデンシャルを選択
3. 認証完了(パスワードレス+ユーザー名レス)
authenticatorSelection: {
residentKey: "required", // Resident Key必須
requireResidentKey: true // 後方互換用
}
eKYCとの統合可能性
現状
犯収法のワ・ヘ・ホ方式は、WebAuthnを直接利用していない。
統合シナリオ
低リスク取引:
WebAuthn(プラットフォーム認証器)
→ 簡便なログイン
中リスク取引:
WebAuthn + 顔認証
→ 生体認証の二重チェック
高リスク取引:
JPKI電子署名([[マイナンバーカード]])
→ 法的効力のある意思表示
サーバー側実装
ライブラリ例
Python:
from webauthn import generate_registration_options, verify_registration_response
options = generate_registration_options(
rp_id="example.com",
rp_name="Example Corp",
user_id="user-123",
user_name="user@example.com"
)
verification = verify_registration_response(
credential=credential_from_client,
expected_challenge=challenge,
expected_origin="https://example.com",
expected_rp_id="example.com"
)
Node.js:
const { generateRegistrationOptions, verifyRegistrationResponse } = require('@simplewebauthn/server');
const options = await generateRegistrationOptions({
rpName: 'Example Corp',
rpID: 'example.com',
userID: 'user-123',
userName: 'user@example.com',
});
const verification = await verifyRegistrationResponse({
response: credentialFromClient,
expectedChallenge: challenge,
expectedOrigin: 'https://example.com',
expectedRPID: 'example.com',
});
関連
- FIDO2 - WebAuthnの上位概念
- [[JPKI]] - 日本の公的認証基盤
- 公的個人認証 - 電子署名による認証
- eKYC - 本人確認への応用可能性
- リスクベースアプローチ - 認証レベル選択
- [[顔認証]] - 生体認証の一種