パスワードにさよなら?スマホ・PCで顔や指紋認証でログイン Passkey認証ができるWebサイトの作りかた
こんにちは 技術戦略部のあずまです。
エンジニアの皆様は、ログイン機能が必要なサイトの「ログイン方法」の要件として何を思い浮かべますか?
多くの方はID/パスワードでの認証を一番に思いつくのではないかと思います。
ID/パスワード認証はポピュラーな認証方法ですが、パスワードの漏洩からの不正アクセスのリスクや忘れてしまったときの不便さがあります。
そこで今回は、Passkey認証のできるサイトの作り方をフロントエンドの実装にフォーカスしてご紹介したいと思います。
(本記事は昨年12月末に社内勉強会で行ったPasskey勉強会をもとに作成しました。)
Passkeyとは?
Passkeyとは、「FIDO/WebAuthn Level3」の実装につけられた名前で、スマートフォンやパソコンなどの端末に保存された認証情報を利用してパスワードレスでログインを行う機能です。
WebAuthnとは、ユーザー名、パスワードの組み合わせの代わり認証器に保存したクレデンシャルを用いてユーザー認証を行う技術のことです。
WebAuthnについては弊社テックブログに別の記事がありますのでご興味のある方はぜひご覧ください。
ここで行われる認証について、ざっくりと説明すると
- ランダムな文字列をサーバからクライアントに送る
- 受け取ったクライアントは自分の鍵で暗号化して返す
- サーバは帰ってきた暗号化された鍵を合鍵で開ける(公開鍵暗号)
- 開けばOK、開かなければNG
といった流れで認証が行われます。
Webauthnの進化
WebAuthnにはLevel2が元々あり、こちらは下記の通り少々使い勝手の悪いものでした。
- スマホを買い換えたらクレデンシャルは登録しなおし
- パソコンのログインにスマホは使えない(USB接続の指紋認証タイプのものを使う)
- 1つのクレデンシャルは別端末では使用できない
この仕様ですと、スマホを買い換えるたびにいろいろなサイトで再登録が必要になることやパソコンでログインするには専用端末が必要など、ユーザビリティを考えるとパスワード認証を選択せざるを得ないと思います。
それがLevel3になり利便性が向上しました。
- デバイス間でBluetooth通信をして、他のデバイスのログインに使える
- PCでのWEBサイトへのログインにスマホの指紋認証、顔認証が使える
- 単一のクレデンシャルに複数デバイスを紐づけられる
- iCloudキーチェーンなど
- 1つのデバイスに登録すれば、同一のiCloudアカウントでログインするiOS,iPad OS, MacOSデバイスにクレデンシャルが共有される。
- クレデンシャルの共有にはエンドツーエンドの暗号化が行われるので安心
対応端末の普及状況
今回行った勉強会では参加者に実際にPasskeyを使ったログインを試してもらおうと思ったため、事前に利用端末のアンケートを実施しました。
すると、驚くことに参加者全員がPasskey対応端末を保有していました。
また弊社のECサービスのスーパーデリバリーへのスマートフォンからの最近のアクセスを分析すると、約82%が対応端末(iOS16以上 もしくは Android 9以上)となっていました。
この数値から、利用者数を考えた時のPasskey導入のハードルは低いのではないかと思います。
実際の動作
実際のPasskeyの動作については、下記のApple社の動画が非常に参考になります。
一度ご覧いただけると使用感がつかみやすいと思います。
https://developer.apple.com/videos/play/wwdc2022/10092/ (1:45~)
Passkey クレデンシャル生成・登録の実装
今回は既存のID/パスワード認証のサイトにPasskeyログイン機能を追加するという想定で実装を進めていきます。
既にログインやユーザの管理といった機能は実装されているので、既存のユーザとFIDOクレデンシャルの連携を行う事をPasskeyの登録と以下記載します。
また、バックエンドは構築済みの物を配布しますので、フロントのコードのみの実装と紹介とさせていただきます。
https://github.com/YA2KM/passkey
今回は、バックエンドにNodejsを採用しました。
下記リンクにSimpleWebAuthnを使用しないフロント側の実装例がありますが、公開鍵暗号を扱う必要があるなど、少し試してみるのにはハードルが高いのではないかと思います。
https://web.dev/passkey-registration/
上記の通り、自分で1から実装するには難解なPasskeyですが、Nodejs向けのライブラリであるSimpleWebAuthnを利用することで簡単に導入することが可能です。
サーバーサイドの実装に関しては、本記事ではご紹介いたしませんがご興味のある方は下記ドキュメントをご参照ください。
https://simplewebauthn.dev/docs/packages/server
注意
SimpleWebAuthn自体の安全性については弊社では担保しておりません。
実際のプロダクトではFIDO Certifiedに記載のある製品を利用される事をお勧めいたします。
登録に必要なコード
ログイン後のページにある、「WebAuthnの登録を開始する」ボタンを押したときの処理から作成していきます。
完成形
views/index.ejs
registerButton.addEventListener('click', async () => {
const resp = await axios.get('/api/getRegistrationOptions')
console.log(resp)
const attResp = await startRegistration(resp.data);
console.log(attResp)
const verificationResp = await axios.post('/api/verification', attResp);
const verificationJSON = verificationResp.data
console.log(verificationResp)
})
- 登録に必要な情報の取得
const resp = await axios.get('/api/getRegistrationOptions')
まず初めに登録に必要な情報をサーバサイドから取得します。
ここにはサービスを識別するIDや、多重登録防止のため過去に登録したクレデンシャルのID、後ほど使用するチャレンジなどが含まれています。
2.ブラウザのPasskey登録機能を呼び出す
const attResp = await startRegistration(resp.data);
次にこの情報をSimpleWebAuthnに渡します。
フロント、バックエンドともにSimpleWebAuthnで実装しているためそのまま渡すだけで簡単に利用することができます。
実際のPasskey認証のUIなどは、ブラウザ側に実装されており、自分で書いたjsで呼び出すことも可能ですが、今回はライブラリが呼び出しを行ってくれます。
https://developer.mozilla.org/ja/docs/Web/API/Navigator/credentials
ここで別のデバイスを選択すると、QRコードが表示されます。
このQRコードを対応デバイスで読み込むことでPasskeyの登録が開始します。
今回はiPadで読み込みを行いました。
端末に搭載されている認証デバイスに応じて、ここから先、何で認証するかは端末により変わるのですが、今回はiPad Proを使用したため顔認証が求められます。
3.生成された情報をサーバに渡す
const verificationResp = await axios.post('/api/verification', attResp);
最後にサーバ側にクライアントが生成した情報を送って登録は完了です。
サーバ側はここで生成された認証情報をDBに保存し、ログインの際に参照します。
Passkeyでのログインの実装
ログイン画面にある、「webauthnでログインする」ボタンから実装していきます。
完成形
views/login.ejs
loginButton.addEventListener('click', async () => {
const name = document.querySelector("#name").value
const resp = await axios.get('/api/getAuthenticationOptions', {
params: {
name
}
})
console.log(resp)
const attResp = await startAuthentication(resp.data);
console.log(attResp)
const authenticationResp = await axios.post('/api/authentication', attResp, {
withCredentials: true
});
window.location.href="/"
})
- ログインしたいアカウントの情報をサーバに送り、チャレンジを取得する
const name = document.querySelector("#name").value
const resp = await axios.get('/api/getAuthenticationOptions', {
params: {
name
}
})
まず初めに、サーバにユーザidを送信し、チャレンジを取得します。
ここではユーザの特定にユーザidを使用していますが、ログインしたい特定のユーザを取得できればこれ以外でも構いません。
2.ライブラリに渡し、ブラウザのPasskeyログイン機能を呼び出す
const attResp = await startAuthentication(resp.data);
こちらも登録同様すべてライブラリにお任せです。
登録時同様、ダイアログが表示されるのでそれに沿って進めるとQRコードが表示されるのでスマホで読み込みます。
すると、顔認証が求められ無事認証されるとブラウザに戻ります。
3.サーバに認証情報を送る
const authenticationResp = await axios.post('/api/authentication', attResp, {
withCredentials: true
})
最後にサーバに認証情報を送ります。
このサイトではセッションをつかってログイン状態を管理していますので、必ずwithCrednetialsをtrueにする必要があります。
4.トップページに遷移
window.location.href="/"
そしてトップページに遷移します。
未ログインの状態ではログインページにリダイレクトされますが、正しくログインできていればトップページが表示されます。
まとめ
このように、ライブラリを利用することでフロント側だけであれば数十行でログイン処理を実装することができます。
パスワードを覚える必要のないPasskey認証、非常に便利なので普及すると良いですね。
弊社では月に2回フロントエンドの勉強会を行っています。だれでも自由に(もちろん新卒でも!)枠を取ることができます。
ラクーングループは一緒に働く仲間を絶賛大募集中です。
こういった勉強会にご興味のある方はぜひ、こちらからエントリーお待ちしています!