🦊

RPがFirefox Androidのパスキーをサポートするときの覚え書き

2024/12/12に公開

この記事はDigital Identity技術勉強会 #iddance Advent Calendar 2024 12日目の記事です。

こんにちは。毎度パスキーの話ばかりですみません。そして今回もパスキーのお話です。
今年夏頃に以下の記事を拝読し、興味深いFirefox Androidの仕様について知ることができたのでRPとしてサポートする場合に考慮した方がよさそうなことを書きます。
https://wontfix.blogspot.com/2024/08/android-14-credential-manager.html

Android Firefoxの仕様

冒頭紹介したブログによると、Firefox Androidはパスキー登録時にauthenticatorSelection.residentKeyの値によってパスキーを生成する方法が異なるようです。まとめると以下のようになります。

residentKey 挙動
required Credential Managerによる同期パスキー作成
discouraged FIDO APIによるデバイス固定パスキー作成
preferred FIDO APIによる同期パスキー作成※

なお、パスキーでの認証時はCredentials Managerを利用しているような挙動となっていました。※
※Android 15, Firefox 133.0.2で確認
(ブログ内では明記されておらず、手元の検証端末で表示されたUIで判断しています。間違っていたら教えてください。)

FIDO APIで作成されるパスキーの注意点

サードパーティパスワードマネージャーでパスキーが作れない

Android 14以降ではCredential Managerを通じてサードパーティパスワードマネージャーでパスキーを作成管理できるようになりました。一方、FIDO APIの方ではこの機能を利用できません。

FIDO API Credential Manager

GPMでしかパスキー作れない
その他のオプションから
サードパーティパスワードマネージャーを選択可能

transportの値が["internal","cable"]になる

transportとは、AttestationResponse内に含まれる「そのクレデンシャルを作成した認証器がクライアントデバイスとの接続可能な方法」です。例えば、クライアントデバイスとUSBやNFCで接続可能なセキュリティキーの場合はtransport=["usb","nfc"]だったり、iOS(Safari)やAndroid(Chrome)などのスマートフォンであれば認証器がデバイス内にあり、かつQRコード読み取りで開始するhybridフローによる接続をサポートしているのでtransport=["hybrid","internal"]だったりします。"cable"(caBLE)というのはこの"hybrid"の以前の名称です。

ここで登録されたパスキーは、同期パスキーはGPMで管理、デバイス固定パスキーの場合はデバイス内で管理され、従来通りそのデバイスでのパスキーの認証に利用できますし、hybridフローでも問題なく動きます。しかし問題になるのはこのtransportをサーバから受け取ったPCなどのブラウザ側です。具体的にSafari※において、transport=["hybrid"]の場合はhybridフローのみが発動しますが、transport=["cable"]の場合は認証器の候補にセキュリティキーが含まれます。 このような挙動を回避するためにauthenticatorAttachment="platform"を指定しているようなRPでは、この挙動は把握しておくと良いと思います。対策としてはRPが配信するallowCredentialsのtransportsのcableをhybridに上書きする黒魔術しかおもいつきません。

transport=["hybrid"] transport=["cable"]

hybridフローのみ発動
hybridフローとセキュリティキーが発動候補

※ Safari 18.2で確認

ThrowされるDOMException

普段WebAuthn APIで開発するときによく見かける例外は、主にユーザキャンセルや認証に利用できるパスキーのないNotAllowedErrorや、excludeCredentialsで指定されたパスキーが存在したときのInvalidStateErrorです。あとは、限定的ですがAndroid Chromeでデバイス固定パスキーの画面ロック解除時にタイムアウトになった際のNotReadableErrorとか。一方Firefox Androidでは一般のユーザ操作によってそれ以外のDOMExceptionが発生するケースがあります。

登録時の挙動

  • 登録時residentKey=requiredのケース(Credential Manager)
    • platform認証器の登録画面で登録キャンセル -> AbortError
    • cross-platform認証器の選択画面/登録画面で登録キャンセル -> UnknownError
  • 登録時residentKey=prefferd(FIDO API)
    • (platform/cross-platform問わず)登録キャンセル -> NotAllowedError
  • 登録時residentKey=discouraged(FIDO API)
    • platform認証器の登録画面で登録キャンセル -> UnknownError
    • cross-platform認証器の選択画面/登録画面で登録キャンセル -> NotAllowedError

認証時の挙動

  • 認証時のケース(デバイス固定パスキーがallowCredentialsに含まれていたケース)
    • platform認証器の認証画面で認証キャンセル -> NotAllowedError
  • 認証時のケース(Credential Manager)
    • アカウント選択画面での認証キャンセル -> AbortError
    • platform認証器での認証キャンセル -> UnknownError
    • cross-platform認証器の選択画面/認証画面での認証キャンセル -> UnknownError

タイムアウトの挙動

WebAuthn APIの挙動として、RPの指定するtimeoutの値が適切な範囲でない場合は、クライアント内で自動的に「適切な値」に書き換えられます。(参考: WebAuthn L3 5.1.3. Create a New Credential)そして、Android Firefoxではこのtimeoutの値を超過するとTimeoutErrorがthrowされます。Chrome(Credentials Manager)やSafariではこの「適切な値」がかなり長いようで基本的にタイムアウトエラーは起こりません。一方でAndroid FirefoxにおいてはFIDO APIを利用する登録ケースではRPが指定した値をほぼ解釈してくれるようでした。また、認証時においてもデバイス固定パスキーを利用するケースでも同様な挙動が見られました。
よってAndroid Firefoxでrpの指定したtimeoutでTimeoutErrorがthrowされうるケースは以下です。

  • residentKey=prefferd/discouragedでパスキー登録を行うケース
  • 認証時のallowCredentialsにデバイス固定パスキーのkeyIdが含まれているケース

まとめ

Android Firefoxの仕様的にRPが注意すべき点は以下です。

  • residentKey=prefferd/discouragedを指定しているRPではAndroid Chromeと異なる挙動になる
  • AbortError,UnknownError,TimeoutErrorの例外ハンドリングに注意

サービス内でサポート環境として明記しないとしても、これらの仕様でもUXが破綻しないようなサービス設計にしていきたいですね。

年末年始はパスキー周りの書籍とか特集などの予定がありメジャーな知識はそこで扱うだろうと思い、マイナーな部分を攻めようとしたら仕事の調査報告書みたいになっちゃいました。

以上、Digital Identity技術勉強会 #iddance Advent Calendar 2024、12日目の記事でした。
https://qiita.com/advent-calendar/2024/iddance

Discussion