Help us understand the problem. What is going on with this article?

Firebase Authを Nuxt + Railsの自前サービスに導入する

この記事は Firebase Advent Calendar 2019 19日目の記事です🎉

はじめに

今年、個人開発で ゆるすけ というサービスをリリースしました!
image.png

自分がお出かけするときに「誘うほどじゃないけど、誰か行かない?」と可愛い画像をTwitterに投稿して「ゆるくスケジュールを共有する」サービスです。

勉強会の参加予定なんかにも相性が良いと思うので、ぜひ使ってください。(ちゃっかり宣伝)

さて、このサービスは Nuxt.js と Ruby on Rails で作られてますが、認証に Firebase Auth を使ってます。
少しハマりどころもあったので、将来同じような構成をトライする人のために知見を共有したいと思います!

認証周りの構成

こんな感じになります。

image.png

(ちなみに認証に Firebase Auth を使ったのはSPA で Twitter ログインのコールバックを管理するのが面倒だったからなのですが、これはこれで結構ハマったので今となってはどっちでもよかったかもと思ってます笑)

Nuxt.js に Firebase Auth を導入する

たくさん記事があるのでざっくり 割愛 します!
「Firebase Auth Nuxt」
「Firebase Auth Vue」
などで検索してみてください。

自前のバックエンド(Rails)と連携する

Knock を導入する

通常の Rails アプリでは Sorcery や Devise を使いますが、
今回は Firebase Auth & API モードなので、バックエンド側の認証を独自で実装する必要があります。
とはいえ1から全て実装するのは大変・・・
と何かいい Gem がないか探していたところ、 Knock という Gem を発見しました。

authenticate_usercurrent_user などのおなじみのメソッド使えます🙌

導入方法は README をご確認ください。
nsarno/knock

Firebase Auth の JWT 検証の仕組みを追加する

公式で用意されている 「Firebase Admin SDK」 を使うと Firebase Auth のトークンを検証できますが、

残念ながら Ruby 版は用意されていません😭
image.png

対応言語でモジュール作って部分的に呼び出す、とかも考えたのですがちょっと面倒臭い・・・

というわけで、自前で( OpenSSL で)検証部分を実装することに。

Knock 自身にも JWT を検証する機能もついて今すが、検証用の公開鍵に変化がない前提になっています。
Firebase の公開鍵は定期的に変更されるので、都度取得する必要があります><

そのため Knock の機能は使わず、以下のように Knock のメソッドをオーバーライドして検証することにしました!

application_controller.rb
module Knock::Authenticable
  def define_current_entity_getter(entity_class, getter_name)
           # 中略
           response = client.get("https://www.googleapis.com/robot/v1/metadata/x509/securetoken@system.gserviceaccount.com")
           jwks_raw = response.body
           JSON.parse(jwks_raw).each do |_key, key_string|
             jwks_string = key_string.gsub("-----BEGIN CERTIFICATE-----", "").gsub("-----END CERTIFICATE-----", "").delete("\n")
             Knock.token_signature_algorithm = "RS256"
             Knock.token_public_key = OpenSSL::X509::Certificate.new(Base64.decode64(jwks_string)).public_key
             begin
               @payload = Knock::AuthToken.new(token: token).entity_for(entity_class)
               break if @payload.present?
             rescue
               next
             end
             # 後略

ただ毎回鍵を取りに行ってしまいレスポンスが遅くなるので、
実際には Faraday を使って鍵をキャッシュしています。

ログイン・新規登録の仕組みを作る

User モデル等に以下のメソッドを作ると、
新規ユーザーなら新規登録、登録済ならログインされます。

models/user.rb
def self.from_token_payload(payload)
  // Userが入れば取得
  user = find_by(sub: payload["sub"])

  // いなければ新規作成
  user || create!(sub: payload["sub"],
                 user_name: payload["name"][0..29],
                 remote_image_url: payload["picture"].sub(/_normal\./, "_bigger."))
end

ここまで実装できれば、あとはいつも通り authenticate_usercrrent_user を呼び出すだけ!

まとめ

  • Rails バックエンドで Firebase Auth 使うのはちょっと大変だった
  • とはいえ、上記の仕組みさえ作ってしまえばあとは SorceryDevise と変わらず使える
  • せっかく Auth と連携できたので、いつか Auth 以外の Firebase 機能と連携したい
tomoeine
宮崎の山の中でリモートフリーランス🌴 Laravel, Rails, Vue.js など。 宮崎のWeb系フリーランス集団"てげほげ"所属で勉強会の企画・運営してます。宮崎のエンジニア盛り上げたい💪 ゆるくスケジュール共有するゆるすけ→https://yurusuke.com
https://yurusuke.com
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした