Rails API×Firebase authの場合、Railsは何をすべきなのかを考えた【設計編】

9 min読了の目安(約5500字TECH技術記事

概要

  • フロントエンド:nuxt.js
  • API:rails apiモード
  • 認証:firebase auth

のような構成でrails側にてどのような認証に関わる実装が必要となるのかを調べてまとめてみました。
長くなりそうだったので、設計編と実装編にわけてあります。
なお、今回の内容はあまりrails関係なくなってしまったので、その他のサーバーサイド実装でも基本的な考え方は一緒かと思います。

ご指摘は真摯に受け止めて学習・修正いたしますので、何かありましたらフィードバックお願いします。

※なお、以下firebase authとfirebaseは同義で使用しています

前提

Firebase authについて

とても簡単にいうと、ログイン系の認証をgoogleのfirebaseさんが肩代わりしてくれます。
フロントエンドでログイン処理を動かすとgoogleのポップアップが出てきて、認証が完了すると、ユーザー情報や検証用のトークンが返却されます。
メールアドレス/パスワード認証をはじめとして、googleアカウントや各種SNSによる認証も可能になります。
また、firebaseの管理画面からユーザーの管理も可能。
個人レベルならとても便利だし、何よりユーザーのコアな情報を自前で持つ必要がないのでとても安全です。

以下公式ドキュメントが親切ですし、mizchiさんの記事からもとても有用性がわかります。
https://firebase.google.com/docs/auth?hl=ja
https://mizchi.dev/202008172159-firebase-authentication

フロントの実装

nuxt×firebase authの基本的な実装は割愛。
調べるとすでに色々な方が記事を書かれているのでそちらを参考にすると良いと思います。
大体の流れは

  • firebaseにアプリを登録(無料)
  • アクセスキー等を取得し、環境変数に格納
  • firebaseのモジュールをnuxtにimportして、アクセスキーを用いてログイン処理を実装
  • 返却されたユーザー情報(トークン含む)をnuxt側で保持(自分はvuexを使用)

ちなみに本番環境にはvercelを使いましたがこちらもとても便利でした。

railsですべきことと、firebase authおよびjwtについて

railsは何すればいいの??

フロントエンドの実装まではなんとか終わったのですがここで、
「あれ、認証情報をfirebaseが持ってるとなると、railsでユーザーを使用する処理はどうすればよいの??」
という疑問が湧いてきました。

railsを使用する場合、deviseを用いることが極めて多いです。
railsでフロントも含めたアプリケーションを作る場合は、認証に関わる全てをdevise単体でやってくれますし、apiモードでもdevise-jwtdevise_token_auth を併用していく構成が多いのではないでしょうか。

だがしかし、devise-jwtやdevise_token_authを利用する場合、どちらもapiサーバー内でユーザーを認証してトークンを返却するのであって、今回のようにapiサーバーが認証サーバーを兼ねない場合、少し不適合な気がします。自前でpwを保持するわけではないので。

ということで、rails側で必要なことは
フロントエンドからfirebase authの認証情報を受け取り、railsで保持しているユーザーに紐づけてapiの処理を行うこと
として実装を考えていきます。

firebase authが返却する情報とトークンの検証

まずrails側での認証実装に先立って、認証に使えるものを確認しましょう。
フロントでfirebase authでの認証が通ると以下のような情報が返却されます。

// 自分のアプリではpluginを作ってラップしています
import firebase from '@/plugins/firebase'

// 今回はgoogleアカウントでのログイン
const provider = new firebase.auth.GoogleAuthProvider();
firebase.auth().signInWithPopup(provider).then(function(result) {
	const user = result.user;
	console.log(user);
}).catch(function(error) {
	// 略
});
// console.log(user)の結果
// 色々割愛したりしていますが、とりあえず雰囲気だけ
user: {
	// 略
	displayName: "awakei"
	eb: null
	email: "awakei@example.com"
	emailVerified: true
	// 略
}

こんな感じでfirebase auth での認証に成功すると、googleアカウントの名前やemail、アバターなどが返ってきます。(実際はもっと長いです)

サーバーサイドでもfirebase系のサービスを使用するともっと楽に認証系を実装できるっぽいですが、そもそも今回はfirebase authしか使わないので、railsでのidトークンの検証が必要です。
詳細はこちらを見ていきます
https://firebase.google.com/docs/auth/admin/verify-id-tokens?hl=ja

ドキュメントには下記のようにあり、以下のように取得した idToken をバックエンドで検証する必要があるとしています。

firebase.auth().currentUser.getIdToken(/* forceRefresh */ true).then(function(idToken) {
 // Send token to your backend via HTTPS
 // ...
}).catch(function(error) {
 // Handle error
});

そもそも なぜidトークンを検証する必要があるのか
それはフロントエンドからサーバーサイドに送られてきた認証情報が 改竄されていないことを確認するため です。
そして、 idTokenは何者なのか
上記を解決するためには以下の二つの技術がポイントとなります。
それは

  • JWT
  • デジタル署名

です。

JWT(Json Web Token / 読み方は"jot")

結論からいうと、先ほど取得した idToken はJWTと呼ばれるものです。
細かい説明は割愛しますが、JWTは

  • 署名の検証に必要な情報を表すヘッダー
  • 送信したい情報を格納したペイロード
  • ヘッダーとペイロードの情報の署名

の三つの部分からなっています。
実際はこんな感じです。(例は https://jwt.io からの引用)

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

↓3つの部分をわかりやすくするためにdottで改行したもの

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.
SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

一見するとわかりづらいですが、ヘッダーとペイロードに関してはjsonをBase64urlでエンコードしたものなので、簡単にデコードできます。(以下はrubyでの例です)

# decode.rb
require "base64"
require "json"

header = Base64.decode64('eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9')
JSON.parse(header)
# => {"alg"=>"HS256", "typ"=>"JWT"}

payload = Base64.decode64('eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ')
JSON.parse(payload)
# => {"sub"=>"1234567890", "name"=>"John Doe", "iat"=>1516239022}

JWTに関してざっくりと把握したい場合は以下の記事がとてもわかりやすかったです。
https://techblog.yahoo.co.jp/advent-calendar-2017/jwt/

デジタル署名

JWTで重要になるのは 署名 の部分です。
というのも、先ほど見せたように、ヘッダーとペイロードは第三者でも簡単に復号できてしまうので、改竄もできてしまう可能性があります。
そこで改竄されていないかを検証するために用意されているのが、署名部分です。

デジタル署名は

  • メッセージが改竄されていないこと
  • メッセージが正しく送信者から送られていること

を確かめる技術です。

今回の場合においては、メッセージの送信主体はfirebaseであり、

  • firebaseが送信したidTokenであること
  • 改竄されていないidTokenであること

を検証する必要があるため、署名の検証を利用します。
そしてその署名こそがJWTの署名部分となるわけです。
この辺りの暗号技術に関しては、結城浩著『暗号技術入門』が本当にわかりやすいのでおすすめです。

※ちなみに検証用のモジュールがfirebase公式から提供されているわけですが、なんとrubyはないので、今回のように色々調べる必要ができてしまった、という経緯です。

今回のまとめ

話が細かくなってしまいましたが、上記をまとめると

  • firebase authからJWTの形式で認証情報(idToken)が返却される
  • railsでフロントから渡されたidTokenを、firebase authのものかどうか検証する必要がある

となります。

次からは実際の実装を行なっていきたいと思います。

参考

以下の文献および記事を参考に執筆しました。

文献

  • 結城浩『暗号技術入門 第3版』2015年, SBクリエイティブ株式会社

記事