どーも、ritou です。
先日、こんなイベントに参加しました。
10/3&4の FIDO イベント、「fidcon 勝手に Meet up!」&「WebAuthnもくもく会」まとめ #idcon #fidcon - Togetter https://t.co/6Xima0AxDM via @togetter_jp
— nov matake (@nov) 2018年10月10日
ブラウザの対応状況などがわかってきたところで、そろそろ実サービスに導入する際に引っかかりそうなところを整理しておきたいというお気持ちです。
いきなり全部書くのは大変なので、最初は割とシンプルにできそうな既存ユーザーへの Authenticator 登録のあたりから考えます。
追記:Yahoo! JAPAN が WebAuthn + Android Chrome による指紋認証を用いたログインを導入したのでそれを意識しつつ更新してます。
前提
登場人物はこんな感じです。
- あるサービス = WebAuthn の RP
- ユーザー = あるサービスに登録済みのユーザー
- パスワードを設定しているかもしれない
- 既に確認済のメアド / SMS番号を持っているかもしれない
- 登録できる Authenticator : 複数登録可能. 生体とかPIN(UserVerification)で単体でユーザー認証が達成できるやつ
汎用的な話にしたいので、ユーザーの前提を少し緩めにしてます。 が、パスワードレスをうたいたいので Authenticator は UserVerification 必須にしてます。 では、あるサービスのユーザーが Authenticator を登録するまでの流れを整理しておきましょう。
Authenticator 登録の流れ
サービスが行う一連の流れをざっくり書くと、以下のようになりそうです。
- 既存の認証方式を用いて一度ユーザーを認証する : パスワード確認、メール / SNS + 認証コードなど
WebAuthn のisUserVerifyingPlatformAuthenticatorAvailable()
を用いて対応している Authenticator を持っているかを判定- WebAuthn の
navigator.credentials.create()
を呼び出し、ユーザーは Authenticator を操作 - WebAuthn の
navigator.credentials.create()
のレスポンスを検証し、ユーザーに紐付ける - Authenticator 追加完了時にメールなどで通知を送る
この一連のフローは割と一般的な "デバイス紐づけフロー" と似ていると思います。
1 について、ログイン方法が1つ増えることはサービス内で重要な設定変更であるため、この処理に入る前にはパスワード認証における「パスワード確認」相当の、ユーザーを再確認する方法が必要でしょう。
特徴的なのは、紐づけられる Authenticator を持ってるかどうかを確認する必要があるだろうと思い、2を入れてます。
が、その Authenticator がユーザーに紐づいているかまではこの関数ではわからないことには気をつけないといけないですね。
(追記)↑ここで
要件次第だとは思いますが、isUVPAAをユーザー検証出来るAuthenticator を持っているかのガード句的に使用すると、
— Yoshikazu Nojima (@shiroica) 2018年10月11日
Cross-Platformなユーザー検証出来るAuthenticatorは見落とされてしまうので注意が必要かと思いますー。
というコメントをもらいました。
isUserVerifyingPlatformAuthenticatorAvailable()
は platform
な Authenticator の判定となるので cross-platform
な Authenticator も対象としたい場合は使ってはいけませんね。
WebAuthn Relying Parties use this method to determine whether they can create a new credential using a user-verifying platform authenticator. Upon invocation, the client employs a client platform-specific procedure to discover available user-verifying platform authenticators.
ということでこの処理はなかったことで。
navigator.credentials.create()
の呼び出しのところでは、
- 今回想定しているのは MFA ではないので
UserVerification
をrequired
にする - ユーザーIDなども Authenticator に保存する Resident Key の機能を用いてユーザー識別子の入力をスキップしてログインさせたい気持ちがあるので、
requireResidentKey
をtrue
にする
とかが必要そうです。詳細は省略します。
navigator.credentials.create()
のレスポンス検証してユーザーと紐づけるところでは、
- Attestation に含まれる Authenticator 情報の一部を使って判別できるようにする
- ユーザーに名前をつけさせ、登録した日時や環境などと一緒に表示して判別できるようにする
などの工夫をしておかないと、後から Authenticator の無効化などを考える時にユーザーに識別させるのが面倒になりそうです。 個人的には上記の2つの案を組み合わせたらバランス取れるかなと思いますが、最初に載せたイベントでは Google の方が "エンプラをのぞいて Attestation 使うな" とお達しがありました。
Yahoo! JAPANではユーザーに名前をつけさせて管理しているようです。 C向けは Attestation を使わず、ユーザーに名前をつけさせるのがスタンダードになるか注目です。
ちなみに、yubico のデモサイトでは、登録完了時に Attestation に含まれる値を表示に用いています。
"deviceProperties": { "deviceId": "(略)", "displayName": "Security Key by Yubico", "deviceUrl": "https://www.yubico.com/products/yubikey-hardware/fido-u2f-security-key/", "imageUrl": "https://developers.yubico.com/U2F/Images/SKY.png" },
トラッキングに使えてしまうのよくないのはその通りですが、実際にユーザーに紐づけるとなるとちょっとだけ情報が欲しくなるので、悩ましいですね。
5として、メールなどの登録完了通知を入れています。 アカウントに対する認証方式の追加はとても重要な処理なので、万が一の場合でも本人に気づきを与える仕組みは必要でしょう。
この流れを使う具体的なフローは2種類ぐらいあるかなと思っています。
1. ユーザーが自発的に設定管理機能から登録
ユーザーが "設定 -> セキュリティ -> 新しいログイン方法" みたいなところで自発的に登録するケースです。
2で対応している Authenticator がない時にヘルプへの誘導をしたり、現在の設定状況をみて紐づけられている Authenticator の上限チェックを行うことになるでしょう。
3の前に現在の設定状況をみて紐づけられている Authenticator の上限チェックを行うなど、サービス側で紐付け可能かの判断が入ることになるでしょう。 3, 4 の処理を経て対応する Authenticator がなかった場合もヘルプへの誘導をしたり、という形式になりそうです。
2. ユーザーがログインしたタイミングで環境などを判定して登録フローに誘導
ユーザーがその時点のログイン方法でログイン完了のタイミングでフックして、Authenticator の登録フローを強制 / 任意で誘導 するようなユースケースもありえるかなーと思っています。
2では Authenticator が利用できる状態であるだけではなく、ユーザーに Authenticator が紐づいていないとか、過去にユーザーがこの誘導を拒否した履歴がないみたいな条件にマッチした時、Authenticator の登録へと誘導するようなイメージです。
ここでも3の前にユーザーに Authenticator が紐づいていないとか、過去にユーザーがこの誘導を拒否した履歴がないみたいな条件にマッチするかどうかを判断して Authenticator の登録へと誘導するようなイメージです。
どうしたってユーザーは自発的に設定しないものだと思いますので、普及させたかったらこれぐらいやらないと「新しい認証方式、誰も使ってくれないわ...」みたいな状況になりそうです。
まとめ
最初なので既存ユーザーに Authenticator を紐づけるところから UX を考えてみましたが、そんなに新しい感じはなかったですね。 次回はログインフローを取り上げる予定です。こちらは Resident Key を用いた Account Chooser とか、もう少し考えることがありそうです。
ではまた!