はじめに
この記事は、Hamee Advent Calendar 2019の15日目のエントリです。
最近はRailsの傍らTerraformをごにょごにょしています。Terraformなんもわからん。
最初に結論
タイトルは言い過ぎました。正確には
RubyでもFirebase AuthenticationからAdmin SDKのようにユーザーの取得や更新はできる!!
(最後にUserManagementの一部機能のサンプルコードあり)
※今回は一部機能しか実装していませんが、がんばればAdmin SDKは作れます。
Ruby+Firebase使ってます
みなさんFirebase使ってますか?
私はここ一年くらいで初めて、とあるプロジェクトでFirebaseを使ってみて、いまさらながらあまりの便利さと機能の豊富さに震えました。認証もプッシュ通知もFaaSもデータベースもなんでもあるやん・・・すごい(語彙力)
こんなすばらしいプロダクトを利用できることに感謝しながら、Railsなサーバーと連携する形でFirebaseを使っていたんですが、あるとき機能改修でuidを使ってRubyからFirebase Authenticationにいるユーザー情報を取得したいケースがでてきました。
Gemがない
さて実装しようと思ったら、ないんですよ。Firebase公式のAdminSDKにRuby実装が。まあ、Rubyのエコシステムなら3rd PartyのGemくらいあるやろーと思って探してみたところ、あれ・・・ない?
firebase-rubyというGemはあるんですが、Firebase Database REST APIのラッパーになっていて、Authenticationへのアクセス機能は持っていない。firebase-authといういかにもな名前のGemも、idToken(Firebaseの認証トークン)を使えばユーザー情報の取得はできるのですが、uidを使ってユーザー情報が取得できないので今回のやりたいことに合わず断念。
諦めかけていたそのとき・・・
不本意だけど、あまり時間もかけていられないし公式のAdminSDKがあるnodeを間に立てて、Rails -> node -> Firebaseという構成でアクセスする実装を進めていました。
ちょうどアドカレネタを探していたところだったのもあって、諦めきれずに調べていたら、How to access account info of firebase auth?といういかにもそれっぽいissueを発見。issue内で取得できたよーって書いてあるじゃないですか!!!
結果、google-api-clientを使えばRubyからでもAdminSDK相当のことができそうなことがわかり、実際に試してみたところ無事にuidでユーザーが取得できました。プロジェクトにもフィードバックして無駄にnodeを経由することを回避できたのでした。
アドカレ駆動開発
前置きが長くなりましたが、以下手順とサンプルコードです。
前準備
- サーバーに Firebase Admin SDK を追加するを参考に、サービスアカウント用の秘密鍵ファイルを生成・ダウンロードしておく
- google-api-client Gemをインストールしておく
サンプルコード
require 'google/apis/identitytoolkit_v3'
# 前準備の1で用意したjsonファイルのパス
service_account_key_json = "/path/to/your/serviceAccountKey.json"
# Googleの認証用サービスの初期化
service = Google::Apis::IdentitytoolkitV3::IdentityToolkitService.new
# 認証用クレデンシャルの生成
service.authorization = Google::Auth::ServiceAccountCredentials.make_creds(
json_key_io: File.open(service_account_key_json),
scope: [
# 認可が必要なスコープを列挙する。今回はidentitytoolkitのみ
# ここにFirebaseの各種サービスを指定することでUserManagement以外も利用可能
'https://www.googleapis.com/auth/identitytoolkit',
].join(' ')
)
# 取得したいFirebaseのUIDを定義(予めFirebaseUIDはRuby側で取得してある想定)
uid = 'target firebase uid'
# 認証サービスへのリクエストの生成(uidでのユーザー情報の取得)
# ※引数が配列な点に注意(複数uidを渡せるが今回は1つとしている)
request = Google::Apis::IdentitytoolkitV3::GetAccountInfoRequest.new(local_id: [uid])
# リクエスト実行
account = service.get_account_info(request)
# ユーザーの取得
firebase_user = account.users[0]
# メールアドレス
firebase_user.email
FirebaseのGemで探していたから見つからなかっただけで、FirebaseもGoogleのサービスの一つなんだからGoogleのGemという切り口で探してみたところたどり着けたのでした。視点の切り替え大事。
あと、諦めなくてよかった
ちなみに更新もできます
uid以外にも電話番号やメールアドレスでのユーザー取得や更新もできます。
# frozen_string_literal: true
source 'https://rubygems.org'
git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
gem 'google-api-client'
require 'google/apis/identitytoolkit_v3'
module FirebaseAdmin
module Auth
class Client
def initialize(service_account_key_json_path)
@service = Google::Apis::IdentitytoolkitV3::IdentityToolkitService.new
@service.authorization = Google::Auth::ServiceAccountCredentials.make_creds(
json_key_io: File.open(service_account_key_json_path),
scope: [
'https://www.googleapis.com/auth/identitytoolkit',
].join(' ')
)
end
def get_user(uid:)
get_account_info(local_id: [uid])&.users&.first
end
def update_user(uid:, params:)
update_params = { local_id: uid }.merge(params)
request = Google::Apis::IdentitytoolkitV3::SetAccountInfoRequest.new(update_params)
@service.set_account_info(request)
end
def get_account_info(params)
request = Google::Apis::IdentitytoolkitV3::GetAccountInfoRequest.new(params)
@service.get_account_info(request)
end
end
end
end
こんなファイルを用意しておいて
require_relative 'firebase_admin/auth/client'
service_account_key_json_path = '/path/to/serviceAccountKey.json'
client = FirebaseAdmin::Auth::Client.new(service_account_key_json_path)
# メールアドレスでの検索
account = client.get_account_info(email: [検索したいメールアドレス])
# 電話番号での検索
account = client.get_account_info(phone_number: [検索したい電話番号])
# メールアドレスの更新
uid = '更新対象のUID'
client.update_user(uid: uid, params: { email: 'update.email@example.com' })
とすればUID以外での検索や更新なども可能です。
Firebase Authenticationに認証は任せているが、Railsからメールは送りたいといった場合でも問題なく実現できます。Rubyだからって諦めなくていいんです。
まとめ
素直に公式のAdminSDK実装がある言語でFirebaseを使おう
RubyからでもFirebaseAdminSDK相当の機能は実現できる
今回紹介したのはUserManagementの一部だけでしたが、scopeの指定をすれば他の機能も実現できそうなので、時間を見つけて実装してみようと思います。