はじめに
Automatic Passkey Upgrade(パスキーへの自動アップグレード)というものを実装したのですが、いくつかの詰まったポイントがあったので記事という形で供養共有します。
Automatic Passkey Upgrade とは?
Automatic Passkey Upgrade(パスキーへの自動アップグレード)とは、特定の条件を満たせばパスキーをユーザーの明示的な操作なしに自動でパスキーを作成する機能のことです。Conditional Create(条件付き作成)とも呼ばれています。
主な条件は下記の通りです。
- パスワードマネージャーにパスワードが保存されている
- パスワードがパスワードマネージャー経由で最近使用されている
下記のChromeのブログに概要がまとまっています。
詰まりポイントを一挙紹介!
実装自体はとても簡単です。パスキーの作成の際に呼び出すnavigator.credentials.getにmediation: 'conditional'をつけて呼び出すだけです。なのですが、実際にやろうとすると詰まりポイントがいくつもあります。私が引っかかった詰まりポイントを順に書いていって、苦しみを追体験していただきます!
どこに実装すべきなのか分からない
Automatic Passkey Upgrade はログイン直後に行う必要がありますが、フロントエンド側でWebauthn APIを叩く必要があるため、バックエンド側にログイン情報を投げたあと直接リダイレクトなどは出来ません。どうにかしてログインしたあとにフロントエンドに制御を返す必要があります。
型定義がない
悲しいですがTypeScriptの型定義がないです。MDNにもドキュメントがありません。いい感じにしましょう。
NotAllowedErrorが出るが理由が不明
Safariの場合はMacのコンソールを見ればログが出ます。私はiCloud Keychainを有効にしろと言われました。
Chromeでこの現象に詰まっている方はまず下記のChromiumのIssue Trackerのコメントを読むと良いと思います。
要点をまとめると下記の通りです。
-
chrome://device-logを見るとログ出る - そのデバイスでパスキーを作成したことがある
そしてChromeのデバイスログを見ると、こちらはポリシーでGPM(Google Password Manager)が無効化されていることが原因でした。
Chromeで謎のエラーが出る その1
以降の動作確認&ソースコードの確認をChromeの140.0.7339.128で行っておりますが、最新のソースコードはログの文面・レベル・量など色々変わっているようでした。
SafariはiCloud Keychainを有効化すればすんなり動きましたが、Chromeはそう甘くありません。こんどはPasskey upgrade request failed: 0なるものが出ます。
エラーコードを数字だけ出されてもなにも分からないので、Chromiumのソースコードを直接確認しに行きます。
こちらのエラーは、下記の部分から出ています。
ここから具体的に0がどのようなエラーコードなのかを調べます。
RequestErrorは以下のようなもののようです。
エラーコードは0なので、kEnclaveNotInitializedに該当するようです。
kEnclaveNotInitializedはEnclaveStateなるものがエラーの時に出るもので、それは下記の関数でチェックをしています。
このまま掘っていっても難しくて全然わからない...
でも名前から察するに、エンクレーブが初期化されてないことが原因っぽいことまで分かりました。
ここで「そのデバイスでパスキーを作成したことがある」という条件が実は効いてきます。私はよく読んでおらず、パスキーを作成していなかったのですが、パスキーの初回作成時にはPINの作成や入力を求められます。おそらくこの時になんか色々やってエンクレーブを初期化しているのでしょう。実際にパスキーを手動で作ると、このエラーは出なくなります。
Chromeで謎のエラーが出る その2
そろそろうまく動いて欲しいのですが、今度はPasskey upgrade request failed: 4エラーが出てしまいました。
上記を参照すると、オプトアウトをしてしまっているようです。Google Password Managerに「パスキーを自動作成してすばやくログイン」という設定があります。これをオンにしないとこのエラーが出ます。(もしかしたらデフォルトでオンかもしれません)
なぜか認証に失敗する
ようやくうまく動いたと思ったのも束の間、バックエンド側で認証に失敗していることが分かりました。
冒頭にパスキーには原則的にユーザーに明示的な操作が必要だということを書いたと思います。その際に、touchIDや顔認証などなんらかの認証が行われることも多いです。ここの挙動は、userVerificationというものの値で制御可能です。userVerificationの詳細は下記の記事を参照してください。
さて、当たり前なのですがパスキーへの自動アップグレードはユーザーの確認は当然ないために、そのユーザー確認(UV)フラグはfalseで渡ってきます。しかし、バックエンドの実装によってはUVフラグがfalseで渡ってくると問答無用で失敗してしまうので、きちんとそこを考慮した実装にする必要があります。
UVフラグの確認を常にスキップする実装は避けるべきで、パスキーの自動アップグレードの場合のみ、UVフラグとユーザー存在(UP)フラグをfalseでも通せるような実装が必要だと思います。
おわりに
(あんまりいないと思うけど、)自分のような初心者でパスキーへの自動アップグレードを実装することになった方への助けになると幸いです。間違っているところがあったらご指摘いただけるとありがたいです。