Firebase Authentication を使う場合、ユーザーモデルとテストはどうする?
Rails で個人用の英単語帳アプリを開発しています。開発環境は Rails 7.0.4 + Ruby 3.1.2 です。
ユーザ認証に Firebase Authentication を導入すると、フォームに入力した email と password を Firebase Authentication API に渡して返り値を受け取るだけで認証が実装できます。認証のためにユーザーモデルを作成する必要はありません。
ただし、ユーザごとのデータを保存するテーブルの関連付けとしてユーザーモデルは必要になります。私の場合、英単語帳を flaschcards というテーブルに保存しているため、「誰が登録した英単語か」を識別するためにユーザーモデルを作成することにしました。ユーザーを作成するために外部の Firebase Authentication API を実行する必要があるため、(Devise 利用時など内部で完結する場合と違い)テストの方法も一工夫する必要が出てきました。
以下は Firebase Authentication を使った場合のユーザーモデルとテストの実装についてまとめたものです。同様の構成を考えている方の参考になれば幸いです。最期に Devise との個人的な比較と所感を載せています。
実装の流れ
大きな流れとしては以下になります。Firebase Authentication API を叩いた時に返ってくる localId (Firebase の管理画面上では User UID と表記される) をユーザーモデルに格納して利用します。
- FIrebase authentication を利用して認証機能を実装する(省略)
- ユーザーモデル(User)を作成する
- Firebase authentication の API で返される localId で User を作成する
- ユーザごとのデータを持つフォームを修正する(省略)
- テストを作成する(ユーザ作成のために外部の Firebase Authentication API の利用が必要ため、モデルやコントローラーのテストではなくシステムテストで行う)
ユーザーモデル(User)を作成する
$ bundle exec rails g model User firebase_local_id:string $ bundle exec rails g migration AddUserIdToFlashcards user:references
app/models/user.rb
class User < ApplicationRecord has_many :flashcards (... snip...)
app/models/flashcard.rb
class Flashcard < ApplicationRecord belongs_to :user (... snip ...)
Firebase authentication の API で返される localId で User を作成する
app/controllers/home_controller.rb
class HomeController < ApplicationController before_action :set_user_data, only: %i[signup login] def signup uri = URI("https://identitytoolkit.googleapis.com/v1/accounts:signUp?key=#{Rails.application.credentials.firebase_api_key}") response = Net::HTTP.post_form(uri, "email": @email, "password": @password) data = JSON.parse(response.body) session[:data] = data session[:firebase_local_id] = data["localId"] if response.is_a?(Net::HTTPSuccess) User.create(firebase_local_id: data["localId"]) redirect_to flashcards_path, notice: "Signed up successfully" end end def login (... snip ...)
app/controllers/application_controller.rb
class ApplicationController < ActionController::Base helper_method %i[current_user authenticate_user] def authenticate_user redirect_to home_login_path, notice: 'You must be logged in to view your data.' unless current_user end def current_user @current_user ||= session[:firebase_local_id] @current_user_id ||= User.find_by(firebase_local_id: session[:firebase_local_id]).id end end
テストを作成する(ユーザ作成のために外部の Firebase Authentication API の利用が必要ため、モデルやコントローラーのテストではなくシステムテストで行う)
test/system/flashcards_test.rb
require "application_system_test_case" class FlashcardsTest < ApplicationSystemTestCase setup do @user = FactoryBot.create(:user) @flashcard = FactoryBot.create(:flashcard) visit home_signup_url fill_in "email", with: "foo@example.com" fill_in "password", with: "abc123" click_on "Submit" end (... snip ...)
Devise と Firebase Authentication の比較
個人的な結論
Firebase Authentication のほうが扱いやすいように感じました。理由としては API を叩くだけで完結するのと、自分で情報を持たないからです。Devise を利用したユーザーモデル(User)は内部にメールアドレス、暗号化されたパスワード、その他接続時の情報を抱えるようです。接続時の情報など(例: current_sign_in_ip)は安易に流出しないようにデフォルトでは Trackable が無効になっています。こうしたガードレールがあるので、基本的には安全だと思いますが Devise の実装をきちんと理解していない限り、ブラックボックスの部分が残ってしまうように思います。内部に情報を持たず API だけで完結する Firebase のほうが扱いやすい、と感じたのはこうした理由からです。
個人的な比較
Devise | Firebase Authentication | |
---|---|---|
透明性 | きちんと理解していないとブラックボックスな点が残る | 基本的に API を使うだけなので単純明快に感じる |
情報の見つけやすさ | Deviseのドキュメントやネットの記事はあふれている | 記事は多くないのでモデルやテストは自作する必要がある(簡単) |
心理的安全性 | 内部に情報を抱えるので不安な点が残る(理解していれば大丈夫) | 内部に情報を抱えないので不安にならない |
ユーザーモデル(User)の大きさ | 肥大化しがち | 使い方にもよるが他テーブルへユーザIDを関連付けるだけなら Firebase authentication が返す locaIId だけでいい |
比較時のスクリーンショット
Devise
Firebase Authentication