何をやったか
最近の仕事柄興味があったのと、WEB+DB PRESS Vol.114の特集2を読んだこともあって、理解を深めるためにWebAuthnでの公開鍵登録(今回はサインアップを兼ねる)、認証だけできる簡単なWebアプリを作りました。リポジトリのREADMEに様子のGIFアニメを貼っています*1。今回はChrome 80とTouch IDで試しています:
このボタンをクリックするとHerokuにデプロイできます。リポジトリのREADMEにも同じボタンを置いています:
デプロイ後に環境変数 WEBAUTHN_ORIGIN
に https://<Herokuアプリ名>.herokuapp.com
を追加してください。
理解できた点
WebAuthn自体の詳しい説明は、Web上のリソースを参照したほうがよいので、今回はとくに書いていません。
登場する各ロールの役割
WebAuthの認証フローを見ると用語が多いですが、概ね次のように理解しました。
- Authenticator
- ユーザーエージェントからアクセスされる認証器
- ハードウェアトークン(Yubikey、Titanなど)やデバイス埋め込みの認証機構(Touch IDなど)
- Relying Party (RP)
- Authenticatorからもらった情報をもとに認証するサーバ
- ブラウザから公開鍵とattestation(認証器自体の正当性証明)を受け取り、公開鍵をアカウントと紐付けて保存する
- ブラウザからassertion(認証時の署名など)を受け取り、認証する
実装の感触
個人的にRailsだとさっと実装できるので、今回はRailsでやりました。webauthn-rubyとwebauthn-jsonの組み合わせで問題なく実装できます*2。実装項目は次のような感じです。
サーバ
- クライアントがWebAuthn APIへ渡すためのオプションやチャレンジ値を返すAPIを追加する
- 公開鍵登録時は
GET /webauthn/credential_creation_options
WebAuthn::Credential.options_for_create
を使う
- 認証時は
GET /webauthn/credential_request_options
WebAuthn::Credential.options_for_get
を使う
- 公開鍵登録時は
- 送られてきたアカウント名、公開鍵、attestationから、アカウントを作成して公開鍵をそのアカウントに紐付けて保存するAPIを追加する
POST /users
WebAuthn::Credential.from_create
/WebAuthn::PublicKeyCredentialWithAttestation#verify
を使う
- 送られてきたアカウント名とassertionから、パスワードレス認証するAPIを追加する
POST /session
WebAuthn::Credential.from_get
/WebAuthn::PublicKeyCredentialWithAssertion#verify
を使う
クライアント
- サインアップ用フォーム
- サーバからもらったオプションを
navigator.credentials.create()
に渡して認証器からattestationをもらいサーバへ送る
- サーバからもらったオプションを
- ログイン用フォーム
- サーバからもらったオプションを
navigator.credentials.get()
に渡して認証器からassertionをもらいサーバへ送る
- サーバからもらったオプションを
RPサーバは署名、オリジン、チャレンジ値、認証回数の検証などプロトコルに規定されている検証を実行する必要がありますが、ライブラリである程度カバーできると思います。
認証体験
ふだんパスワードマネージャを使ってはいますが、やはりパスワードレスだと認証の体験がかなり簡単に感じました。これで多要素も満たせる(所持、生体)のも便利ですね。