本文へスキップ
ecdsa.com
Node.js · WebCrypto

SimpleWebAuthn

SimpleWebAuthn は Matthew Miller による、TypeScript で最も広く使われているパスキーライブラリです。オプションのジェネレーターとレスポンスの検証関数を備え、デフォルトでユーザー検証を必須とし、アサーション署名は DER のみを受け付け、カウンターの巻き戻りを拒否します。
ライセンス
MIT
確認したバージョン
14.0.3 ·
ソースコード
github.com/MasterKale/SimpleWebAuthn
ドキュメント
simplewebauthn.dev/docs/

互換性の概要

WebAuthn

SimpleWebAuthn は WebAuthn を 2 つのパッケージに分けています。@simplewebauthn/browser は navigator.credentials をラップし、@simplewebauthn/server はリライングパーティが送るオプションを生成し、返ってきたもの(アテステーション形式、COSE 鍵、カウンター、署名)を検証します。チェックでは、標準的な登録とアサーションの JSON を生成するソフトウェア認証器でサーバー側のパッケージを動かしました。これは Python、Go、Java のページで使ったものと同じバイト列です。

最もよくある統合時のバグは、オプションで求める内容と検証で要求する内容の食い違いです。generateRegistrationOptions() のデフォルトは userVerification: "preferred" ですが、2 つの検証関数はどちらもデフォルトで requireUserVerification: true です。ユーザー存在確認しか行っていない認証器(PIN のないセキュリティキー)のアサーションは、User verification required, but user could not be verified で拒否されました。サンプルのように方針を一度決め、両側で同じ設定にしてください。

WebAuthn 仕様が定めるとおり、ES256 のアサーション署名は ASN.1 DER でなければなりません。64 バイトの生の r‖s 署名もそのまま ASN.1 パーサーに渡され、署名のバイト列によって異なるメッセージで失敗します。新しく作成した 60 個の認証情報で最も多かったのは Too big integer、End of input reached before message was fully decoded、Data does not match to ECDSASigValue ASN1 schema. で、いずれもエンコーディングには触れていません。認証器やパスキープロバイダーは DER を生成するため、生の署名は通常、手書きのクライアントか、WebCrypto の sign() を直接呼び出したテストハーネスによるものです。DER ⇄ 生形式の変換ツールで変換してください。high-S の DER 署名は検証を通りました。

保存済みの値より小さい署名カウンターは、警告ではなくエラーになります。検証では Response counter value 3 was lower than expected 5 がスローされ、保存済みの値が 5 のときのカウンター 0 も同様に失敗しました。カウンター 0 で通過するのは、同期パスキーのように常に 0 を報告してきた認証情報だけです。受け取ったカウンターを保存し、巻き戻りが起きたときに認証情報をロックするか、アラートを出すだけにするかは自分で決めてください。バージョン 14 はデフォルトの pubKeyCredParams で ML-DSA-44(COSE -48)を Ed25519(-8)、ES256(-7)、RS256(-257)より先に並べており、Ed25519 の認証情報は登録から認証まで一通り検証できました。

ブラウザーが実際に送信した内容は WebAuthn インスペクターでデコードし、実際の認証器なしでのフローはパスキーサンドボックスで試し、認証器の機種は AAGUID 検索で AAGUID から特定してください。

事実とその根拠となるチェック

以下の内容はドキュメントからの転記ではありません。2026年10月20日に、チェック content/libraries/verify/simplewebauthn/run.sh がコンテナイメージ node:24.22.0-bookworm-slim 内で SimpleWebAuthn 14.0.3 を Node.js 24.22.0 で実行しました。このページの内容はその出力と一致していなければならず、各事実には該当する出力行を示しています。

  • デフォルトの COSE アルゴリズム

    -48-8-7-257

    generateRegistrationOptions() は COSE アルゴリズムを -48(ML-DSA-44)、-8(Ed25519)、-7(ES256)、-257(RS256)の順に並べます。

    チェックによる記録
    fact webauthn-default-algorithms = -48, -8, -7, -257
  • デフォルトのアテステーション

    none

    アテステーションの伝達方法のデフォルトは none です。

    チェックによる記録
    fact webauthn-attestation = none
  • デフォルトのユーザー検証

    preferred

    authenticatorSelection.userVerification のデフォルトは preferred です。

    チェックによる記録
    fact webauthn-user-verification = preferred
  • デフォルトのレジデントキー

    preferred

    authenticatorSelection.residentKey のデフォルトは preferred です。

    チェックによる記録
    fact webauthn-resident-key = preferred
  • デフォルトでのユーザー検証の強制

    yes

    requireUserVerification: false を渡さない限り、2 つの検証関数はどちらも UV フラグを要求します。UP だけが立った登録とアサーションは、それぞれ User verification was required, but user could not be verified と User verification required, but user could not be verified で拒否されました。

    チェックによる記録
    fact webauthn-uv-enforced-by-default = yes
    evidence webauthn-uv-enforced-by-default: registration with the UV flag clear: throws User verification was required, but user could not be verified
  • アサーション署名のエンコーディング

    der

    ASN.1 DER 署名を含む正しい ES256 アサーションは検証を通りました。

    チェックによる記録
    fact webauthn-assertion-encoding = der
  • 生の r‖s 形式のアサーション署名

    rejected

    同じアサーションでも 64 バイトの生の r‖s 署名では、署名のバイト列によって文言が変わる ASN.1 の解析エラーがスローされました。

    チェックによる記録
    fact webauthn-raw-signature = rejected
  • high-S のアサーション署名

    accepted

    s を n − s に置き換えた DER 署名も検証を通りました。

    チェックによる記録
    fact webauthn-high-s = accepted
  • 署名カウンターの巻き戻り

    rejected

    保存済みの値より小さいカウンターでは Response counter value 3 was lower than expected 5 がスローされました。保存済みの値が 5 のときのカウンター 0 も拒否されましたが、保存済みの値が 0 のときの 0 は検証を通りました。

    チェックによる記録
    fact webauthn-counter-regression = rejected
    evidence webauthn-counter-regression: counter 0 after a stored 5: throws Response counter value 0 was lower than expected 5; counter 0 after a stored 0: verified
  • Ed25519 の認証情報

    supported

    Ed25519(COSE -8)の認証情報で、登録と認証に成功しました。

    チェックによる記録
    fact webauthn-ed25519 = supported
    evidence webauthn-ed25519: verified

使い方

検証関数がデフォルトで適用するのと同じユーザー検証ポリシーを求める、登録用と認証用のオプションを生成します。
import { generateAuthenticationOptions, generateRegistrationOptions } from "@simplewebauthn/server";

const rpID = "example.com";

// Registration: the options ask for UV only as "preferred"...
const registration = await generateRegistrationOptions({
  rpName: "Example",
  rpID,
  userName: "user@example.com",
  authenticatorSelection: { residentKey: "required", userVerification: "required" },
});

// ...while verifyRegistrationResponse() and verifyAuthenticationResponse()
// require the UV flag by default. Ask for "required" up front (as here), or pass
// requireUserVerification: false when a security key without a PIN is acceptable.
const authentication = await generateAuthenticationOptions({ rpID, userVerification: "required" });

console.log("algorithms:", registration.pubKeyCredParams.map((param) => param.alg).join(", "));
console.log("uv:", registration.authenticatorSelection?.userVerification, authentication.userVerification);
console.log("challenge bytes:", Buffer.from(authentication.challenge, "base64url").length);

スニペットのテストハーネスは、すべてのサンプルをコンテナイメージ node:24.22.0-bookworm-slim 内でそのまま実行し、出力が一致しなければ実行は失敗します。

セットアップ
npm init -y >/dev/null && npm install --no-audit --no-fund @simplewebauthn/server@14.0.3
コマンド
node usage.mts
一致すべき出力(正規表現)
algorithms: -48, -8, -7, -257\nuv: required required\nchallenge bytes: 32

ライブラリ