📣

「WebAuthn Signal API」はパスキーの認証UXを改善するやつ

2024/09/16に公開

はじめに

2024/9、Chrome CanaryでSignal APIが使えるようになりました!👏
https://github.com/w3c/webauthn/wiki/Experimenting-with-the-Signal-API-on-Chrome
Signal APIは「RP上に保存されているアカウント情報(ユーザ名、登録済みパスキー)」をクライアントのパスキーマネージャー※に伝える機能です。この機能によって「RP側でユーザ表示名を変更した際にパスキーのPasskey AutofillのUI上のユーザ表示名も更新する」「RP側でパスキーを削除したらパスキーマネージャー側でも削除する」といったことが実現できます。
本記事では、この機能によってどんな問題が解決されるのか?またどのように利用するか?を整理します。

※ この記事ではユーザのパスキーを管理するエコシステムを「パスキーマネージャー」と表現します。Google パスワード マネージャー, iCloud キーチェーン, 1Passwordなど。

なぜWebAuthn Signal APIが必要なのか?

「パスワードの自動入力」を利用していて以下のような経験はないでしょうか?

  • Webサービス上でメアドを変更したがパスワードマネージャー上のメアドは古いまま
  • 変更済みの過去のパスワードを自動入力してしまい認証に失敗した

これはパスワードマネージャーが、Webサービスでのデータ更新を把握できないためです。ただ、昨今ではWebサービス上のデータ更新時に、パスワードマネージャーのデータも自動更新してくれるので、もうこのような体験はあまりないかもしれませんね。

パスキーにもPasskey Autofill(Conditional UI)という自動入力に似た仕組みがあります。

Passkey Autofillの例(webauthn.io

しかしパスキーにはパスワードマネージャーのようにWebサービス上の変更に追従する機能はありませんでした。

そのため、Webサービス上でデータとパスキーマネージャー側のデータを一致させるには、ユーザ自身がパスキーマネージャーのUIを操作する必要があります。しかし多くのユーザにとってそのような操作は非常に複雑かつ面倒です。それをRP側でできるようにするのがSignal APIです。

WebAuthn Signal APIの使い方

Signal APIはPublicKeyCredential.signal*として以下の3つの機能があります。

  1. RP上で変更されたパスキーのメタデータをパスキーマネージャーに通知する。
  2. RP上で利用できるパスキーID一覧をパスキーマネージャーに通知する。
  3. RP上で利用できないパスキーIDをパスキーマネージャーに通知する。

これらの機能はRPに関連するパスキーの情報をブラウザへ通知するだけであり、その結果パスキーマネージャーでどのように処理されるかは各パスキーマネージャーの実装次第です。例えば、削除済みのパスキーIDを通知した際、パスキーマネージャー側でそのパスキーが削除されるのか、それとも自動入力などのUI上で表示されなくなるだけなのか、といった選択肢があります。またSignal APIのレスポンスからどのような処理が行われたかをRPは知ることができません。これはWebAuthn APIでよくあるユーザプライバシーのためですね。

余談ですが、これらの機能がブラウザ上で実装されているかどうかはdemoページでは以下のように検証されていました。Safari 18 Betaで実装されたgetClientCapabilitiesでは判定できないのでしょうかね?(ChromeがgetClientCapabilitiesに現状対応してないからという理由かも。)

const missingApi = [
  "signalUnknownCredential",
  "signalCurrentUserDetails",
  "signalAllAcceptedCredentials",
].filter((api) => {
  return !window.PublicKeyCredential[api];
});

1. パスキーのメタデータの更新を通知する

RP上で変更されたパスキーのメタデータをパスキーマネージャーに通知するためにはsignalCurrentUserDetailsを利用します。
ここでいうパスキーのメタデータは「user.id」「user.name」「user.displayName」があります。ただし、「user.id」はSignal APIでは更新できません(WebAuthnの仕様的にuser.idを変更することはないと思います)。

await PublicKeyCredential.signalCurrentUserDetails({
  /* 更新対象を指定するプロパティ */
  rpId: "example.com",   // RPの識別子
  userId: "M2YPl-KGnA8", // ユーザの識別子
  /* 更新内容 */
  name: "currentemail@relying-party.com",
  displayName: "J. Doe"
});

2. 利用できるパスキーIDをパスキーマネージャーに通知する

現在RP上で利用できるパスキーID一覧をパスキーマネージャーへ通知するためにはsignalAllAcceptedCredentialIdsを利用します。
ここで指定されたもの以外のパスキーはパスキーマネージャーで削除されるか、認証用のUIで非表示になるなどが想定されます。(前述の通りここの挙動はパスキーマネージャー依存です)

await PublicKeyCredential.signalAllAcceptedCredentialIds({
  /* 更新対象を指定するプロパティ */
  rpId: "example.com",
  userId: "M2YPl-KGnA8",
  // このRPで利用できるパスキーの識別子(credentialId)
  allAcceptedCredentalIds: [
    "vI0qOggiE3OT01ZRWBYz5l4MEgU0c7PmAA",
    "Bq43BPs"
  ]
});

3. 利用できないパスキーIDをパスキーマネージャーに通知する

前節とは対照的に、現在RP上で利用できないパスキーIDをパスキーマネージャーへ通知するためにはsignalUnknownCredentialIdを利用します。

PublicKeyCredential.signalUnknownCredentialId({
  /* 更新対象を指定するプロパティ */
  rpId: "example.com",
  // このRPで利用できないパスキーの識別子(credentialId)
  credentialId: "vI0qOggiE3OT01ZRWBYz5l4MEgU0c7PmAA"
});

「利用できるパスキーID一覧の通知」ではなく「利用できないパスキーIDの通知」を使いたいケースはどのようなケースでしょうか?それは未ログインのユーザがパスキーで認証をした際、クライアントから削除済み(または未登録)のパスキーで認証を行われたケースなどが該当します。一度利用されたcredentialIdがこのRP上で利用できないことをsignalUnknownCredentialIdで通知することで、次の認証ではその認証が必ず失敗してしまうパスキーが再度利用されるのを防ぐことができます。

WebAuthn Signal APIを利用するタイミング

前述の3つの機能が実行されるタイミングについて解説をします。
なお、RP側ではこれらをすべて実装する必要はなく、サービスの実現したいUXに応じて必要なものだけ実装することができます。(たぶん)

ユーザデータ(user.name)を更新したタイミング

パスキーメタデータと同じ属性のデータがRP上で変更された場合、signalCurrentUserDetailsを利用することでパスキーマネージャー上のパスキーメタデータも更新することができます。

RP上でパスキーを削除したタイミング

RP上でパスキーを削除したタイミングで、削除されたパスキーIDをsignalUnknownCredentialIdで通知、またはsignalAllAcceptedCredentialIdsで現在RP
で利用できる全てのパスキーIDを通知することによって、次回以降の認証でその削除済みのパスキーが利用されるのを防げます。
どちらかというと後者の方が汎用的でいいんじゃないかと思います。

パスキーの認証が失敗した直後のタイミング

パスキーの認証が失敗したタイミングでsignalUnknownCredentialIdを利用してRP上では利用できないパスキーIDと通知することによって、再度のそのパスキーを利用してしまうケースを防ぐことができます。

ログイン済みセッションでの任意のタイミング

ログイン済みセッションでの任意のタイミング(主に認証成功直後のタイミングなど)で、signalAllAcceptedCredentialIdssignalCurrentUserDetailsを実行することでパスキーマネージャー内のパスキーの状態を最新にすることができます。
なぜユーザ操作によるデータ更新以外のタイミングでも実行をするかというと、ユーザが複数のパスキーマネージャーを利用しているケースがあるためです。例えば、Google パスワード マネージャーを利用しているAndroidでユーザ名を変更するとGoogle パスワード マネージャーのパスキーのメタデータは更新できます。しかしMacで利用しているiCloud キーチェーン上のメタデータは更新されないままです。よって次回Macで認証に成功したタイミングでもパスキーマネージャーのステータスを更新するため、これらのメソッドを実行する価値があります。

実装状況

2024/09/15でのSignalAPIはデスクトップ版のChrome Canaryのみ対応しており、かつGoogle パスワード マネージャーに登録されているパスキーのみ更新されます。

実装UI

Chrome Canary(on Mac OS v130.0.6718.0)でのブラウザUIは以下のような状況です。


signalCurrentUserDetailsでメタデータが更新されたときのブラウザ通知


signalCurrentUserDetailsでパスキーを削除したときのブラウザ通知


signalUnknownCredentialIdでパスキーを削除された通知

この記事を読んでいるようなパスキーマスターな方々はすぐ慣れそうですが、一般ユーザが混乱しないかはちょっと懸念ですね。(signalUnknownCredentialIdの「パスキー管理」、「理解した」はどのような気持ちでクリックすればいいんだろう...)

考察

Signal APIをRPで実装する際に考えた方がよさそうなことを書きます。

RPのパスキー関連データが不必要に公開されないか

認証成功後に無条件でsignalAllAcceptedCredentialIdssignalCurrentUserDetailsに利用するデータをクライアントに送信することは注意が必要そうです。例えば、これらのデータの参照に必要なAccessAssuranceLevel(AAL)に満たしていないのにデータを送信してしまうなど。
ここらへんはSignal APIに限ったはなしではないから流石に大丈夫か...

ユーザが手動で更新したuser.nameを上書きしてしまう

現状パスキーのuser.nameはパスキーマネージャー上で変更可能です。

Google パスワード マネージャーでuser.nameを更新する例

(ここを編集しているユーザはほとんどいないとは思うのですが、)現状のChrome CanaryのsignalCurrentUserDetailsでは強制的にuser.nameを書き換えます。最終的にChromeでどのように実装されるかは不明ですが、もしユーザが編集したメタデータを尊重するRPはこの機能を利用しないという選択はあり...なのかな...?ユーザ側でRPからの更新をオプトアウトできるならRPは懸念せずにすみますが...。

パスキーどこに保存されているかわからん問題

昨今、「パスキーがどこに保存されているかわからなくなった」といった話題がありますが、Signal APIは良い影響があるんじゃないかと思っています。
現状パスキーマネージャーには「利用できないパスキー」と「利用できるパスキー」などが混在する状態です。そこでSignal APIを通じて利用できるパスキーのみがパスキーマネージャーに残るようになれば、「まずパスキーマネージャーに登録されているパスキーを確認してみて」とユーザに案内しやすそうだなと思いました。(もちろん一度ログイン済みにならないといけない、などの制約はあります。)

ただ、根本的な問題として、「そもそもユーザはどのパスキーマネージャーにパスキーが登録されているかわからん」の方が重要そうです。そっちの解決はRPのパスキー管理画面でAAGUIDを元に表示名を作ったり、「パスキー定期便」ですかね。
https://ritou.hatenablog.com/entry/2024/09/14/172410

おわりに

昨今パスキーではPasskey Autofillなどアカウントセレクタを使った認証UXが主流のため、そこで表示される項目が整理されるのはよいことだと思います。
現状はデスクトップ版のChrome Canaryのみ実装されているということで、他ブラウザへ実装されるかはまだ未知数ではありますが、個人的に是非実装されてほしいなと思います。

参考情報

Discussion