完全パスワードレスにするためにパスキーと組み合わせる必要がある技術（前編）

1月28日に、「パスキーのすべて」という本を共著で出版させてもらうことになりました。

https://amzn.asia/d/5aIjy9V

https://www.yodobashi.com/product/100000009003999594/

https://www.maruzenjunkudo.co.jp/products/9784297146535

https://books.rakuten.co.jp/rb/18080194/

諸先輩方のように、毎日ブログを書くことは私にはとても難しいので、3人で共著なのを理由にして、28日の発売まで、3日に1本ぐらいの感覚で、どこかのブログで記事を書き続けるチャレンジをしてみたいと思います。1ヶ月ぐらいならきっと続くことを祈ります。これは2本目。

:::note warn
このブログの内容は個人の見解です。優しいツッコミ歓迎します。
:::

世の中からパスワードを一掃するため、FIDOからさらなる進化（？）を遂げたパスキーですが、それでも、パスワードの完全な撤廃は、パスキー単独では実現し得ません。他の技術を組み合わせることで、パスワードの廃止に限りなく近づけることができます。他の方々のブログと重複する部分も多くあるとは思いますが、改めて、必要なモノを整理したいと思います。

## マジックリンク

多くのサービスは、ユーザ登録時にEメールを入力する必要があると思います。パスワードを使うサービスでは、パスワードを忘れた場合に、Eメールに対して新たなパスワードだったり、再設定用のリンク(URL)を送ります。この再設定用のリンクを、マジックリンクを呼びます。

パスキーに移行した後も、同じように、パスキーにアクセスできない環境からのログインには、マジックリンクを送ることで、メールアカウントにアクセスできる環境であれば、マジックリンクを開くことでログインしてもらえるようにできます。
パスワードであれば、マジックリンクを開くとパスワード再設定画面となり、パスワードを再設定して、再ログインすることが多いですが、パスキーの場合、マジックリンクを開いたとしてもパスキー設定を必須とせず、単にログイン済の状態とする方が良いでしょう。なぜなら、パスキーを作れない、もしくは作成すべきでない環境の可能性があるからです。


### マジックリンクはどうやって作成・保存すべき？

マジックリンクは、自サイトのURLの中に、ランダムな文字列をパラメータとして追加します。この際、ランダムな値を生成する方法は、各言語にある暗号用の乱数生成関数（Javascriptであれば、[Crypto.getRandomValues()](https://developer.mozilla.org/en-US/docs/Web/API/Crypto/getRandomValues) ) を利用して生成したバイト列をBase64URL変換するのが良いでしょう。
また、乱数の長さについては、米国政府のガイドラインである[NIST SP800-63-3Bの5.1.3.2 Out-of-Band Verifiers](https://pages.nist.gov/800-63-3/sp800-63b.html#5132-out-of-band-verifiers) を参考に、最低20ビット以上のエントロビー（ランダム性）が必要で、かつ64ビット未満の場合にはレート制限をするべきと書いてあるので、64ビットの乱数を作成するのが安全でしょう。
また、同ガイドラインには、有効期限は10分とすること、一度利用されたシークレットは無効化することとあります。ここで気をつけるべきは、メールでマジックリンクを送る場合、メールサービスによっては、リンク先の情報をユーザに提示するためにリンクを先に踏んでしまうことがあるため、1回で無効になってしまうリンクはユーザが一生踏めない可能性がある点です。コレばかりは、1回のみという制約を外すだとか、UserAgentなどを見て自動的に開かれたケースは除外するなどの考慮が必要になるかも知れません。導入時には当初は制限を緩めにして、成功率を見ながら都度調整が必要な領域です。

作成した乱数は、専用のテーブルを作って管理することになると思います。乱数、該当のユーザID、有効期限、利用済フラグ ぐらいは必要になると思います。



## メールOTP

マジックリンクは、メールを開ける環境であればワンタップでログインできるので非常に便利です。
ですが、たとえばスマホでしかメールを受信できないのに、他人のPCでログインしたいといった場合には、別の方法が必要です。
その場合には、Eメールで6桁程度の数字を送り、ユーザに入力してもらう方法が考えられます。

6桁数字を乱数として生成した場合、エントロピーは、10の6乗で、1,000,000 (百万）です。ところで、上述のNIST SP-800-63-3Bでは、乱数の強度は最低20ビットと規定されていますが、20ビットとは、2の20乗で、1,048,576 となり、ほぼ同等となります。

マジックリンクと異なり、メールOTPは、メールサービスにアクセスできない環境でもログインできる利点がある一方、ユーザがリアルタイムフィッシングにだまされる可能性もあることに注意が必要です。

また、偶然100万分の1の確立でログインが成功することを狙って、大量のログイン試行が行われる可能性にそなえ、IPベースなどでの一定時間内の回数制限や、Capchaの導入も検討した方がよいでしょう。

## OAuth デバイスフロー

テレビでのログインで時々見る、画面に表示されたQRコードを読んだり、画面に表示された数桁の英数字をスマホやPCに入力することでログインする方法です。
デバイスフローについては、技術的には、川崎さんのエントリを読んで頂くのが一番正確でわかりやすいです。


https://qiita.com/TakahikoKawasaki/items/78eff94cef92741131f0

ここで、「認証なのにOAuth？」と思った方。鋭いです。
一般的には、認証連携につかうのがOpenID Connect(OIDC)であり、OAuthは認可のために使うプロトコルで、これを混同してOAuth認証と言うと、OAuth警察が飛んできます。
ですが、デバイスフローにおいては、テレビやゲーム機などの外部デバイスに対して、アカウントの一部機能を使うことを認可すること ~~になります~~ が多いと思います。誤解を恐れずざっくり言えば、テレビやゲーム機にデバイスフローでログインしたとしても、アカウントの全権限は委譲されません。アカウントの大事な機能（パスワード変更など）にはアクセスできないと思います。なので、認証ではなく認可であると理解しておいてください。

ではなぜ、デバイスフローでログインした端末上では、アカウントの全機能を利用できないのでしょうか。
それは、2つ理由が考えられます。
1つは、フィッシングに弱いことです。メールOTP同様、リアルタイムフィッシングでアカウントにアクセスされる可能性があります。よって、デバイスフローでログインした端末で実行できる機能は制限しておく方が安全です。
2つめとして、共有される端末でアクセス ~~される可能性が高い~~ することが容易であることです。お茶の間のテレビは、家族誰もが利用できます。YouTubeを見るために自分のGoogleアカウントでログインしたが最後、家族のスマホでガチャを買うためにそのアカウントが使われてしまったら、目も当てられません。

## もっと大事なこと

上記の通り、パスキーでのログインだけでは、パスワードの廃止ができないため、マジックリンク、メールOTPやデバイスフローによるログインを提供する必要性があります。
一方で、それら手段は、パスキーの最大の利点である、フィッシング耐性においてはパスキーに劣ります。

ユーザをよりよく守るためには、ユーザがログインした手段に応じて、そのセッションの有効期限を調節したり、セッションにおいてユーザの操作を一部制限したり、高リスクな機能の利用時には、別の認証を求めるといった対応を検討する必要があります。

つまり、ユーザのセッションごとに、ログイン手段が何であったかを記録しておく必要があるのです。
ここで、OpenID Connectをご存じの方であれば答えがもう見えているとは思いますが、何しろ9回分のブログを書く必要があるので、次回に続きます。


### 補足いただきました。

https://sizu.me/ritou/posts/3fndkezs342c


## 後編につづく・・・

https://qiita.com/kkoiwai/items/7804b43a1dbac5296f32