最近スマホアプリの開発は大体firebaseを使うようになっていて、認証もfirebase authenticationを使っておけば、実装がすごく楽になっていると感じます。
既にRuby on railsでwebアプリケーションを開発していて後からJWT認証を付けようとするとちょっと面倒かと思いますが、 firebase-auth-railsというGemを使って比較的簡単にJWT認証を行えるようになったので設定方法を書いてみます。
基本的にREADME通りで動いたのですが、どんな設定が必要なのかチョット補足します。 スマホアプリのバックエンドにこのGemを使ったので、Rails APIモードで実装したコードを記載しています。
- 環境
- 概要図
- ディレクトリ/ファイル構成
- セットアップ
- 実装
- ApplicationControllerへ追記
- ユーザ登録用のコントローラを作成
- ログインが必要な画面のコントローラを作成
- Routes追加
- デプロイ後のオペレーション
- 補足
環境
概要図
ユーザ登録時、以下のような仕組みでユーザを作ります。
ユーザ登録後、例えばマイ書籍一覧画面のような特定のユーザにしか見れない画面を開く時は以下のような仕組みで特定のユーザを判別します。
ディレクトリ/ファイル構成
├── app │ └── controllers │ └── api │ ├── v1 │ │ └── auth │ │ └── registrations_controller.rb │ ├ application_controller.rb │ └ books_controller.rb └── config └── initializers └── firebase_id_token.rb
セットアップ
Redis
Redisをサーバにインストールしておきます。
$ sudo apt install redis-server
内部でfirebase_id_tokenというgemを使っていて、このgemはRedisが必須のためインストールしておきます。 google x509 証明書とその期限を管理するためにRedisを使っているようです。
Gem追加
Gemfileに以下を追記し、
gem 'firebase-auth-rails'
Gem
をインストールしておきます。
$ bundle
実装
イニシャライザーの追加
config/initializers/firebase_id_token.rb
にRedisとfirebabaseプロジェクトの設定を記載します。
FirebaseIdToken.configure do |config| config.redis = Redis.new config.project_ids = ['firebase_project_id'] end
上記概要図④、⑨でRailsサーバ側でfirebaseにJWT認証するので設定しておきます。
ユーザのuidカラムを追加
firebaseのUIDを保存しておくカラムを追加します。(上記、概要図⑤、⑩で使うカラム)
$ rails g migration AddUidToUsers uid:string
できたマイグレーションを実行しておきます。
class AddUidToUsers < ActiveRecord::Migration[5.1] def change add_column :users, :uid, :string end end
$ rails db:migrate
ApplicationControllerへ追記
以下のようにApplicationControllerにinclude Firebase::Auth::Authenticable
, before_action :authenticate_user
の2行を追加しておきます。
# app/controllers/api/v1/application_controller.rb module Api class V1::ApplicationController < ActionController::API include Firebase::Auth::Authenticable before_action :authenticate_user end end
こうすることでV1::ApplicationController
を継承したコントローラのメソッドにアクセス時、HTTPリクエストヘッダに、「Authorization: “Bearer *****************(トークン)”」を入れるだけで、勝手にJWT認証してくれるようになります。(上記、概要図⑨、⑩の箇所)
ユーザ登録用のコントローラを作成
ユーザ登録用のコントローラ(上記概要図④のRailsサーバ側の処理)はこんな感じで作ってみました。
# app/controllers/api/v1/auth/registrations_controller.rb require_dependency 'api/v1/application_controller' module Api module V1 module Auth class RegistrationsController < V1::ApplicationController skip_before_action :authenticate_user def create FirebaseIdToken::Certificates.request raise ArgumentError, 'BadRequest Parameter' if payload.blank? @user = User.find_or_initialize_by(uid: payload['sub']) if @user.save render json: @user, status: :ok else render json: @user.errors, status: :unprocessable_entity end end private def token_from_request_headers request.headers['Authorization']&.split&.last end def token params[:token] || token_from_request_headers end def payload @payload ||= FirebaseIdToken::Signature.verify token end end end end end
ログインが必要な画面のコントローラを作成
Api::V1::ApplicationController
を継承しているので、indexアクセス時勝手にJWT認証(上記、概要図⑨、⑩の箇所)してくれます。
# app/controllers/api/v1/books_controller.rb module Api module V1 class BookssController < Api::V1::ApplicationController def index render json: current_user.books, status: 200 end end end end
Routes追加
routes.rbには以下のように記載しました。
Rails.application.routes.draw do 〜〜〜 namespace 'api' do namespace 'v1' do resources :books, only: %i(index) namespace 'auth' do post 'registrations' => 'registrations#create' end end end end
デプロイ後のオペレーション
サーバにコードデプロイ後は、firebase_id_tokenのREADME に記載してある以下をrails console
で実行し、Googleのx509証明書をダウンロード・確認することで、JTW認証が動くようになりました。
FirebaseIdToken::Certificates.request FirebaseIdToken::Certificates.present? => true
補足
スマホアプリはReact Nativeで実装したのですが、以下のようなコードになりました。 firebaseパッケージを使っています。
[サインアップ時の処理]
// SignupScreen.js サインアップ画面のコード export default class SignupScreen extends React.Component { 〜〜〜 onSignupPress = () => { firebase.auth().createUserWithEmailAndPassword(this.state.email, this.state.password) .then(apiClient.authenticate, authError); // => サインアップ後、apiClient.authenticate 認証処理を実行 } 〜〜〜 }
[ログイン時の処理]
// LoginScreen.js ログイン画面のコード export default class LoginScreen extends React.Component { 〜〜〜 onLoginPress = () => { firebase.auth().signInWithEmailAndPassword(this.state.email, this.state.password) .then( apiClient.authenticate , authError); // => ログイン後、apiClient.authenticate 認証処理を実行 } 〜〜〜 }
[ログイン処理(ログイン時と、サインアップ時で共通)のコード]
class ApiClient { async authenticate() { const token = await firebase.auth().currentUser.getIdToken(true) const data = { token } postRequest('/api/v1/auth/registrations', data,); } }