この記事は Firebase Advent Calendar 2019 19日目の記事です🎉
はじめに
今年、個人開発で ゆるすけ というサービスをリリースしました!
自分がお出かけするときに「誘うほどじゃないけど、誰か行かない?」と可愛い画像をTwitterに投稿して「ゆるくスケジュールを共有する」サービスです。
勉強会の参加予定なんかにも相性が良いと思うので、ぜひ使ってください。(ちゃっかり宣伝)
さて、このサービスは Nuxt.js と Ruby on Rails で作られてますが、認証に Firebase Auth を使ってます。
少しハマりどころもあったので、将来同じような構成をトライする人のために知見を共有したいと思います!
認証周りの構成
こんな感じになります。
(ちなみに認証に 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_user
や current_user
などのおなじみのメソッド使えます🙌
導入方法は README をご確認ください。
nsarno/knock
Firebase Auth の JWT 検証の仕組みを追加する
公式で用意されている 「Firebase Admin SDK」 を使うと Firebase Auth のトークンを検証できますが、
残念ながら Ruby 版は用意されていません😭
対応言語でモジュール作って部分的に呼び出す、とかも考えたのですがちょっと面倒臭い・・・
というわけで、自前で( OpenSSL
で)検証部分を実装することに。
Knock 自身にも JWT を検証する機能もついて今すが、検証用の公開鍵に変化がない前提になっています。
Firebase の公開鍵は定期的に変更されるので、都度取得する必要があります><
そのため Knock の機能は使わず、以下のように Knock のメソッドをオーバーライドして検証することにしました!
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 モデル等に以下のメソッドを作ると、
新規ユーザーなら新規登録、登録済ならログインされます。
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_user
や crrent_user
を呼び出すだけ!
まとめ
- Rails バックエンドで Firebase Auth 使うのはちょっと大変だった
- とはいえ、上記の仕組みさえ作ってしまえばあとは
Sorcery
やDevise
と変わらず使える - せっかく Auth と連携できたので、いつか Auth 以外の Firebase 機能と連携したい