webauthn-rubyでマルチドメイン対応する方法
はじめに
これはGLOBIS Advent Calendar 2025 シリーズ2の17日目の記事です。
こんにちは!グロービスのtsuki_です。
普段は認証システムの開発に携わってます。
初めて記事を書くのでドキドキしていますが、よろしくお願いします!
今回は、Passkey(WebAuthn)をRailsで実装する際に、RP(Relying Party)周りでちょっと引っかかったのでメモとして残しておきます。
具体的には、同じRailsアプリで複数のドメイン(例:example.com と auth.example.com)を扱っていて、それぞれでWebAuthnを使いたいケースです。
webauthn-rubyのInstance Based Configurationを使うことで解決できたので、その方法を共有します。
前提知識
使用ライブラリ
RP ID(Relying Party ID)とは
WebAuthnにおいて、RP IDはサーバー(Relying Party)を識別するためのIDです。通常はドメイン名を使用します。
例えば、example.com でWebAuthnを使う場合、RP IDは example.com になります。
認証器(Authenticator)は、このRP IDと紐づけてクレデンシャル(認証情報)を保存するため、異なるRP IDでは同じクレデンシャルを使えません。
webauthn-rubyのデフォルト設定(Global Configuration)
webauthn-rubyでは、通常initializersで以下のようにグローバル設定を行います。
# config/initializers/webauthn.rb
WebAuthn.configure do |config|
config.allowed_origins = ["https://example.com"]
config.rp_id = "example.com"
config.rp_name = "Example App"
end
この設定はアプリケーション全体で共有され、WebAuthn::Credential.options_for_create などのメソッドで自動的に使用されます。
デフォルト設定の問題点:マルチドメイン環境
例えば、同じRailsアプリで以下の2つのドメインを扱っているとします:
| ドメイン | 用途 |
|---|---|
example.com |
メインサービス |
auth.example.com |
認証専用 |
WebAuthnの仕様上、RP IDを親ドメイン(example.com)に設定すれば、サブドメイン(auth.example.com)からも利用できます。
WebAuthn.configure do |config|
config.allowed_origins = ["https://example.com", "https://auth.example.com"]
+ config.rp_id = "example.com" # サブドメインからも使える
end
しかし、ここで問題が発生します。
auth.example.com で登録したパスキーも、RP IDは example.com に紐づきます。そのため、example.com で認証しようとしたときに、auth.example.com で保存したパスキーも候補として表示されてしまいます。
同様に、stg.example.com や dev.example.com などの検証環境で登録したパスキーも、本番環境で候補として表示されてしまいます。
これはユーザー体験として混乱を招きます。ドメインごとに別々のパスキーを管理したい場合は、RP IDを分ける必要があります。
この問題を解決するのが、次に紹介するInstance Based Configurationです。
解決策:Instance Based Configuration
Global Configurationとの違い
| 項目 | Global Configuration | Instance Based Configuration |
|---|---|---|
| 設定方法 |
WebAuthn.configure で一度だけ設定 |
WebAuthn::RelyingParty.new で都度作成 |
| 設定の数 | アプリ全体で1つ | 複数のインスタンスを作成可能 |
| 用途 | 単一ドメインのアプリ | マルチドメイン、マルチテナントなど |
Instance Based Configurationを使えば、リクエストごとに異なる設定を使い分けることができます。
基本的な使い方
公式ドキュメントによると、Global ConfigurationとInstance Based Configurationは共存できます。
Adding a configuration for a new instance does not mean you need to get rid of your Global configuration. They can co-exist in your application and be both available for the different usages you might have. WebAuthn.configuration.relying_party will always return the global one while WebAuthn::RelyingParty.new, executed anywhere in your codebase, will allow you to create a different instance as you see the need. They will not collide and instead operate in isolation without any shared state.
つまり、既存のGlobal Configurationを残したまま、必要な箇所でインスタンスを作成できます。
- Global Configurationは
WebAuthn.configuration.relying_partyで参照される - Instance Based Configurationは
WebAuthn::RelyingParty.newで作成する - 両者は独立して動作し、状態を共有しない
# config/initializers/webauthn.rb(そのまま残す)
WebAuthn.configure do |config|
config.allowed_origins = ["https://example.com"]
config.rp_id = "example.com"
config.rp_name = "Example App"
end
+ # 必要な箇所でインスタンスを作成
+ relying_party = WebAuthn::RelyingParty.new(
+ allowed_origins: ["https://auth.example.com"],
+ id: "auth.example.com",
+ name: "Example App"
+ )
メソッドの呼び出し方も変わります。
登録(Registration)
- options = WebAuthn::Credential.options_for_create(user: ...)
- credential.verify(challenge)
+ options = relying_party.options_for_registration(user: ...)
+ relying_party.verify_registration(credential, challenge)
認証(Authentication)
- options = WebAuthn::Credential.options_for_get(allow: ...)
- credential.verify(challenge, public_key:, sign_count:)
+ options = relying_party.options_for_authentication(allow: ...)
+ relying_party.verify_authentication(credential, challenge)
注意点
RP IDを変更すると、変更前のRP IDで登録されたパスキーは使えなくなります。
例えば:
- Global Configurationで
rp_id = "example.com"でパスキーを登録していた - Instance Based Configurationで
id = "auth.example.com"に変更 - → 既存のパスキーは
example.comに紐づいているので、auth.example.comでは検証できない
既存ユーザーのパスキーを移行する場合は、再登録が必要になります。
まとめ
- マルチドメイン環境でWebAuthnを使う場合、RP IDを親ドメインに統一すると、意図しないパスキーが候補に表示される問題がある
- webauthn-rubyのInstance Based Configurationを使えば、ドメインごとに異なるRP IDを設定できる
- Global ConfigurationとInstance Based Configurationは共存可能
- RP IDを変更すると既存のパスキーは使えなくなるので、導入時は注意
Discussion