<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>WebAuthn on U-REI.com</title><link>https://u-rei.com/tags/webauthn/</link><description>Recent content in WebAuthn on U-REI.com</description><generator>Hugo</generator><language>ja-JP</language><copyright>2023-2026 Kosuke Uchida</copyright><lastBuildDate>Thu, 13 Aug 2026 04:00:00 +0900</lastBuildDate><atom:link href="https://u-rei.com/tags/webauthn/index.xml" rel="self" type="application/rss+xml"/><item><title>Titanセキュリティキーが登録はできるのにログインだけ失敗する: VaultwardenのWebAuthn 2FAで踏んだ罠</title><link>https://u-rei.com/posts/2026/08/2026-08-12-vaultwarden-webauthn-titan-key-login-failure/</link><pubDate>Thu, 13 Aug 2026 04:00:00 +0900</pubDate><guid>https://u-rei.com/posts/2026/08/2026-08-12-vaultwarden-webauthn-titan-key-login-failure/</guid><description>&lt;p&gt;セルフホストの&lt;a href="https://github.com/dani-garcia/vaultwarden"&gt;Vaultwarden&lt;/a&gt;にGoogle Titanセキュリティキーを2段階認証(FIDO2 WebAuthn)として登録したら、登録自体は一発で成功するのに、その後のログインが必ず失敗する、という不可解な症状にはまった。同じ物理キーはGoogleやwebauthn.ioでは何の問題もなく使える。原因を突き止めるのに数週間かかったので、記録として残しておく。&lt;/p&gt;
&lt;h1 id="症状"&gt;症状&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;Titanキーを2FAとして登録すると成功する(タッチ要求 → 登録完了、というふつうの流れ)&lt;/li&gt;
&lt;li&gt;ログイン時、ブラウザの「セキュリティキーを挿入してタッチしてください」ダイアログが出たまま反応がなく、最終的に&lt;code&gt;NotAllowedError: The operation either timed out or was not allowed&lt;/code&gt;でタイムアウトする&lt;/li&gt;
&lt;li&gt;同じ現象がLinux(Chrome)、Mac mini(Chrome/Firefox)、スマートフォンでも再現。OSやマシンには依存しない&lt;/li&gt;
&lt;li&gt;同じキーをGoogleアカウントやwebauthn.ioに登録すると問題なく動く。キー自体は壊れていない&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id="除外していった仮説"&gt;除外していった仮説&lt;/h1&gt;
&lt;p&gt;原因を絞り込むまでに、以下をひとつずつ潰していった。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;RP ID/ドメインの不一致&lt;/strong&gt; — ブラウザのネイティブダイアログには正しいドメインが表示されている&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ブラウザ拡張機能・広告ブロッカーの干渉&lt;/strong&gt; — 無効化しても変化なし&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;webauthn-connector.html&lt;/code&gt;の&lt;code&gt;Permissions-Policy&lt;/code&gt;ヘッダーに&lt;code&gt;publickey-credentials-get&lt;/code&gt;が含まれていない&lt;/strong&gt; — Caddy側でヘッダーを上書きするテストを本番で実施したが変化なし&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;サーバー側での認証情報の保存/シリアライズ不具合&lt;/strong&gt; — DBに保存された&lt;code&gt;cred_id&lt;/code&gt;はログイン時にそのままバイト単位で一致しており、サーバー側で壊れている様子はない&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;U2F互換用の&lt;code&gt;appid&lt;/code&gt;拡張を無条件に送っていることが原因では?&lt;/strong&gt; — 移行済み(&lt;code&gt;migrated: true&lt;/code&gt;)のクレデンシャルにだけ&lt;code&gt;appid&lt;/code&gt;を送るようパッチしたVaultwardenをビルドして本番にデプロイし、Chromeの内部FIDO/CTAPログ(&lt;code&gt;--vmodule=&amp;quot;*fido*=3,*webauthn*=3&amp;quot;&lt;/code&gt;)で比較したが、タイムアウトまでの挙動パターンは無変化。この仮説は外れ&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;本家Bitwarden(C#実装)との差異&lt;/strong&gt; — &lt;code&gt;WebAuthnTokenProvider.cs&lt;/code&gt;を確認したが、Vaultwardenとほぼ同じ流れ(&lt;code&gt;UserVerificationRequirement.Discouraged&lt;/code&gt; + 無条件の&lt;code&gt;AppID&lt;/code&gt;拡張)で組まれており、Vaultwarden固有のバグという線も薄い&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id="突破口-firefoxで登録し直すとpinを聞かれた"&gt;突破口: Firefoxで登録し直すとPINを聞かれた&lt;/h1&gt;
&lt;p&gt;外堀を埋めていく中で、ふと「登録済みのキーを消して、Firefoxで登録し直してみたらどうなるか」を試した。すると、Chromeでは一度も出なかった&lt;strong&gt;セキュリティキーのPIN入力を、Firefoxは要求してきた&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;このFirefoxで登録し直したクレデンシャルでログインすると、Chrome・Firefoxどちらからでも成功する。念のため2回繰り返して確認した。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Chromeで再登録 → 失敗(何度やっても同じ)&lt;/li&gt;
&lt;li&gt;Firefoxで再登録 → 成功(Chrome/Firefoxどちらのログインも通る)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;「登録時にPINによるユーザー検証(UV)が実際に行われたかどうか」が明暗を分けているのは間違いなさそうだった。&lt;/p&gt;
&lt;h1 id="dbレコードを直接比較する"&gt;DBレコードを直接比較する&lt;/h1&gt;
&lt;p&gt;同じ物理キーを、登録に使うブラウザだけ変えて2回登録し、Vaultwardenの&lt;code&gt;twofactor&lt;/code&gt;テーブル(SQLite)に保存されるレコードを直接比較した。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Chromeで登録(PINなし)&lt;/th&gt;
&lt;th&gt;Firefoxで登録(PINあり)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;user_verified&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;false&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;true&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;counter&lt;/code&gt;(ログイン後)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0&lt;/code&gt;(一度も成功していない)&lt;/td&gt;
&lt;td&gt;ログインのたびに増分&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;クレデンシャルIDの長さ&lt;/td&gt;
&lt;td&gt;160 bytes&lt;/td&gt;
&lt;td&gt;288 bytes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;code&gt;registration_policy&lt;/code&gt;はどちらも&lt;code&gt;discouraged&lt;/code&gt;のまま変わらないが、実際にUVが行われたかどうかでクレデンシャルIDの長さそのものが変わっている。Titanキーは、UVなしで作られた短い方のクレデンシャルではログインの&lt;code&gt;GetAssertion&lt;/code&gt;を完走できない、という個体差(あるいは仕様)を持っているらしい。&lt;/p&gt;</description></item></channel></rss>