
はじめに
こんにちは、@rs_tukki です。
ラクスの開発部では、これまで社内で利用していなかった技術要素を自社の開発に適合するか検証し、ビジネス要求に対して迅速に応えられるようにそなえる 「技術推進プロジェクト」というプロジェクトがあります。
今回、このプロジェクトで「Passkey」に関する検証を行なったので、その結果を報告させていただきます。
- はじめに
- Passkeyとは何か
- 技術的な仕組み
- 他の認証方式との比較
- Spring Bootでの実装
- BtoB SaaSへの導入に向けて
- Passkey導入のメリット/デメリット
- まとめ
- 参考リンク
Passkeyとは何か
基本概念
まず、具体的な検証内容の説明に入る前に、Passkeyについて軽く説明します。
PasskeyとはFIDOアライアンスとW3Cが共同で策定した新しい認証技術です。
従来のIDとパスワードを入力する認証方式とは異なり、ユーザはデバイスのロック解除に使う方法(生体認証やPINコードなど)を使ってWebアプリにログインすることができます。
Passkeyを利用することによるメリットは2点。ユーザ体験を損なわず2要素認証を実現できる点と、複数のデバイス間で認証情報を共有できる点です。
これにより、従来の認証方式と比較して、一定のセキュリティを担保しつつ、ユーザ体験を向上させることが可能になります。
対応環境
Passkeyを利用するには、ブラウザとOSでそれぞれ異なるバージョン要件が必要になります。
バージョンは以下の通りですが、近年の環境であればそれほど意識せずともPasskeyを利用できるかと思います。
ブラウザ要件
- Chrome: バージョン109以上(Windows/Mac/Android/iOS/iPadOS)
- Safari: バージョン16以上(Mac/iOS/iPadOS)
- Edge: バージョン109以上(Chromiumベース)
- Firefox: バージョン122以上
OS要件
- Windows: 10以上
- macOS: 13(Ventura)以上
- iOS/iPadOS: 16以上
- Android: 9以上(Google Play開発者サービス搭載端末)
技術的な仕組み
FIDO2規格の構成
実は、「Passkey」という言葉を定義する場合、広義のPasskeyと狭義のPasskeyという2種類の定義方法があります。
まずは広義のPasskeyについてですが、これは以下2つの技術仕様をまとめた、FIDO2を基盤として構成した認証技術です。
1. Web Authentication (WebAuthn)
W3Cによって標準化された、ブラウザ上で公開鍵認証方式を利用するためのJavaScript APIです。開発者が直接操作するのは主にこのAPIになります。
2. Client to Authenticator Protocol (CTAP)
FIDOアライアンスによって定められた、ブラウザと認証器(デバイスの生体認証センサーやセキュリティキーなど)との間で通信を行うための規格です。WebAuthnが内部で利用しており、開発者が直接意識することは少ないです。
この「広義のPasskey」では、認証情報を認証器(PCやスマホ端末)そのものが保持していました。
そのため、たとえばPCに対してPasskeyを登録していても、同じユーザがスマホ端末からログインしたい場合、また別の端末にPasskeyを登録し直すところから始める必要があります。
一方で「狭義のPasskey」では、上記のFIDO2仕様に加え、これらの情報をクラウド上のパスワードマネージャーに保管することを前提としています。
これにより、複数の端末を利用していても、パスワードマネージャーを共有することで、再登録の手間なく認証を行うことができます。
本記事では、単にPasskeyと呼ぶ場合、この「狭義のPasskey」を指して説明しています。
また、狭義のPasskeyはその特性から、MDC(Multi-Device FIDO Credential)とも呼ばれることもあります。
登録フロー
Passkeyによる認証は、「登録」と「認証」の2つのフローから成り立ちます。
登録のフローは下記の通りです。
- チャレンジの生成: サーバーが一意のチャレンジ(乱数)を生成
- ユーザー検証: デバイスのスクリーンロック解除(生体認証、PIN等)でユーザーを認証
- 鍵ペアの生成: デバイスがドメインに紐づいた秘密鍵と公開鍵のペアを生成
- 秘密鍵の保存: パスワードマネージャー(Google Password Manager、iCloudキーチェーンなど)に暗号化して保存
- 公開鍵の送信: 公開鍵をサーバーに送信し、データベースに保存

ここで重要なのは、ブラウザとサーバの間でやりとりしている情報が
- チャレンジ(乱数)
- 秘密鍵で署名したチャレンジ
- 公開鍵
- 認証器に関するメタデータ
など、機密性の低い情報に限られることです。
従来のパスワード認証におけるIDやパスワード、またユーザ認証に必要な生体情報やPINコードなど、機密性の高い情報は一切やり取りされていません。
これによって、Passkeyではセキュリティを担保しているというわけです。
認証フロー
Passkeyを登録した後は、以下のフローでユーザの認証を行います。
- チャレンジの発行: サーバーが一意のチャレンジを生成
- ユーザー検証: デバイスのロック解除でユーザーを認証
- 署名の生成: 秘密鍵でチャレンジに署名
- 署名の検証: サーバーが公開鍵で署名を検証

認証の場合も、登録時のフローと同様、秘密鍵や生の認証情報など機密性の高い情報はやり取りされていません。
また、加えて以下の仕様によってセキュリティを担保しています。
フィッシング攻撃への耐性
認証器が作成した秘密鍵は、その鍵を作成したサイトのドメイン(=RPID)と紐づいています。
これにより、偽サイトでPasskeyを利用させようとしてもRPIDが異なるため署名することができません。
リプレイ攻撃への耐性
一度サーバが生成したチャレンジは、次回以降の認証に使用することができません。
そのため、認証に成功したリクエストを再現するリプレイ攻撃へも耐性を持っています。
他の認証方式との比較
ここまでPasskeyの仕様について説明してきました。
続いては、他の認証方式とPasskeyとの違いを説明していきます。
多要素認証(MFA)との違い
多要素認証とは、認証に使われる以下の3要素のうち、2種類以上の要素を利用して認証することです。
- 知識情報:パスワードやPINコードなど
- 所有情報:PCやスマートフォンなどの端末そのもの、もしくはクライアント証明書など
- 生体情報:指紋認証や顔認証、虹彩認証など
Passkeyによる認証は、認証器を利用して照合を行うため、認証器という所有情報と、ロック解除に使用する生体/知識情報を同時に利用して認証しています。
二要素認証というと、よくあるパターンはワンタイムパスワードや「ログインしようとしていますか?」といったプッシュ通知を利用する二段階認証ですが、Passkeyでは認証情報を2回入力する手間を省きつつ、二要素認証によってセキュリティを担保できているわけです。
また、先述の通り、認証情報そのものを送信せず、RPIDが異なると認証できないことから中間者攻撃やフィッシング攻撃に耐性がある他、
攻撃者が大量に認証を試行することでユーザのデバイスに通知が大量に送られる、いわゆるMFA疲労攻撃に対しても耐性を持っています。
FIDO1との違い
先ほど、広義のPasskeyはFIDO2仕様を基盤にしていると説明しました。
一方で、認証方式にはFIDO1という仕様もあります。これにはUAFとU2Fの2つの規格があります。
1. UAF(Universal Authentication Framework)
専用のセキュリティキーで生体認証を行うパスワードレス認証です。
公開鍵と秘密鍵を利用して署名を検証する点はPasskeyと同様ですが、PCそのものでは認証ができず、外部機器が必要となります。
2. U2F(Universal 2nd Factor)
上記のUAFに2段階認証の仕組みを加えたものです。
まず最初に通常のパスワードで認証を行ったあと、セキュリティキーを用いた二要素認証を行います。
記載の通り、FIDO1では専用の外部機器が必要なことに対し、PasskeyではOSとブラウザのバージョン要件を満たしていれば
PCやスマホそのものを認証器として利用できるのが大きなメリットです。
Spring Bootでの実装
さて、ここからはWebアプリに実際にPasskeyによる認証を組み込む実装を見ていきます。
システム構成
今回の検証では、以下の技術スタックを使用しました。
- AlmaLinux8 - Apache 2.4.37 - Spring Boot 3.4.5 - spring-boot-starter-security - spring-security-web - webauthn4j-core:0.29.2.RELEASE - PostgreSQL 16.8 - SimpleWebAuthn - 証明書を備えたHTTPS環境
バックエンドの実装に関しては、各言語にPasskey認証を実現するためのフレームワークがあるかと思いますが、
今回は一番手軽な、Javaのspring-security-web + webauthn4j-coreの組み合わせを使用します。
フロントエンドでは、SimpleWebAuthnを利用して、バックエンドと認証器の繋ぎ込みを行います。
実装のポイント
今回の構成では、チャレンジの作成やCredentialの検証など基本的なフローはフレームワークが担ってくれます。
そのため細かい実装内容の解説は他の記事に譲りますが、ここでは特に注意すべきポイントをいくつか解説します。
1. データベース設計
先述のフロー図で、公開鍵等のサーバが保持する情報はDBに保存すると記載しました。
Credentialを作成しDBに保存する処理は自前でinterfaceを実装する必要があるため、以下のようなテーブルを作成しておきます。
Table "public.m_passkey_credential" Column | Type | Collation | Nullable | Default -------------------------------+---------+-----------+----------+---------------------------------- id | integer | | not null | generated by default as identity credential_id | bytea | | not null | user_id | text | | not null | pubkey | bytea | | | attested_credential | bytea | | | attestation_object | bytea | | | Indexes: "m_passkey_credential_pkey" PRIMARY KEY, btree (id) "m_passkey_credential_credential_id_key" UNIQUE CONSTRAINT, btree (credential_id)
それぞれのカラムに格納する内容は以下の通りです。
- id:DB管理用の主キー
- credential_id:各認証器が生成する一意のID
- user_id:Credentialが紐づくユーザのID
- pubkey:認証器が生成した公開鍵
- attested_credential:メタデータを含む、認証器の登録時にクライアントが生成したデータ
- attestation_object:認証器の製造元情報、証明書チェーンなどを含む、認証器の信頼性を証明するデータ
2. ドメイン設定の制約
Spring Securityを用いたWebアプリでは、以下のようなSecurityFilterChainを実装することでPasskeyの仕組みを有効化することができます。
@Bean public SecurityFilterChain securityFilterChain(final HttpSecurity http) throws Exception { http.authorizeHttpRequests(authorizeHttpRequests -> { // リクエスト許可設定(ログイン用URLとwebauthnフレームワークの関連URLは認証を不要とする) authorizeHttpRequests .requestMatchers("/login/**", "/webauthn/**").permitAll() .anyRequest().authenticated(); }) // ログインフォームの設定(デフォルト) .formLogin(formLogin -> {}) // Passkeyに関する設定 .webAuthn(webauthn -> { webauthn .rpName("Passkey") // Passkeyのサービス名。任意の名称でOK .rpId("localhost") // サービスのドメイン名 .allowedOrigins(Set.of( // Passkeyを利用可能なURL(完全一致) "https://localhost:8443" )); }); return http.build(); }
ここで重要な設定が、rpIdです。こちらは先ほど説明した通り、Passkeyを紐づけるドメイン情報です。
指定したドメイン(もしくはそのサブドメイン)以外から認証をしようとした場合、不正な操作としてエラーになります。
そして、このRpIdの指定ですが、以下のような制約があります。
- 単一ドメインのみ: 1つのドメインしか指定できない
- IPアドレス不可: IPアドレスでは動作しない
- HTTPS必須: localhost以外では正式な証明書が必要
そのため、ローカル環境以外では、正式な証明書が発行され、DNSで関連づけられた正式なドメインでしか検証できないということになります。
検証用にサーバを構築している方は注意が必要です。
3. Passkeyの削除
Passkeyはパスワードマネージャーとサーバ上のDBの両方でデータを管理していますが、
このうちパスワードマネージャ上に保存されたデータは、webauthnのAPIで削除することができません。
ブラウザ上の操作で削除可能ものはサーバ側のデータのみであるため、Passkeyを完全に削除したい場合、パスワードマネージャーから手動で操作を行う必要があります。
二段階に分けて削除を行う必要があるため登録時より手間がかかります。ユーザには予め二段階の削除が必要な旨を案内しておくのが良いでしょう。
BtoB SaaSへの導入に向けて
ここまでPasskeyという技術について説明しましたが、ラクスが提供しているようなBtoB SaaSの場合、Passkeyによる認証の需要はまだ少ないです。
Amazon等BtoCのサービスでは徐々に普及しつつあるものの、BtoBでは採用されているサービスはほとんどないのが現状です。
理由としては、ユーザ側がシングルサインオンやSSLクライアント認証などの認証方式を利用しているためそれらで十分というケースが挙げられます。
また、前提として一人当たり1端末(認証器)の利用を前提としているPasskeyは、例えば複数社員で一つの端末を共有している場合などでは相性が悪かったりもします。
しかし、今後ユーザーニーズが変わり、生体認証を含めた二要素認証への理解と需要が高まってきた時には、Passkeyを導入することも検討できるのではないかと思います。
Passkey導入のメリット/デメリット
最後に、Passkeyのメリットとデメリットをそれぞれまとめておきます。
メリット
ユーザビリティの向上
- パスワード記憶が不要
- (指紋認証や顔認証であれば)ワンタッチでログイン可能
- デバイス間での同期が可能
セキュリティの強化
- フィッシング攻撃への耐性
- 総当たり攻撃への耐性
- 認証情報の窃取リスクの排除
運用コストの削減
- パスワードリセットの対応がほぼ不要になる
- (適切に実装されていれば)セキュリティインシデントの軽減に繋がる
デメリット
技術的ハードル
- フレームワークである程度カバーできるとはいえ、セキュリティリスクも考慮すると実装の難易度は高い
- CredentialIDの重複による乗っ取りの可能性
- originやRPIDの適切な設定が欠けていることによるフィッシングの可能性
- ユーザ検証の不備によるなりすましの可能性 etc...
- フレームワークである程度カバーできるとはいえ、セキュリティリスクも考慮すると実装の難易度は高い
ユーザー教育
- そもそも「Passkey」という認証方法に馴染みがないユーザもいる
- マニュアル等で適切なサポートを行わないと、かえってユーザ体験を損ねてしまう可能性がある
互換性の課題
- 非対応環境の存在も考慮しなければいけない
- 特にスマホ端末で、MDM(Mobile Device Management)を利用している場合は互換性がない可能性も
まとめ
ここまで、Passkeyを使った認証方式のメリットと実装方法、またBtoB SaaSでのPasskeyの需要について説明してきました。
Passkeyによる認証は、従来のID/パスワードによるログインと同等かそれ以上のユーザ体験の向上に加え、セキュリティ面でも向上が見込める新時代の認証方式です。
現状のBtoB SaaSにおいてはまだそこまで需要の高い認証方式ではありませんが、
今後、二要素認証に対する理解が深まり、需要が高まってきた時に備え、仕組みや実装について理解しておくとよいでしょう。