Nuxt + Rails + Firebase ユーザー登録 (Rails編)

Nuxt + Rails + Firebase ユーザー登録 (Rails編)

前回の記事でNuxt とFirebaseを使用したユーザー認証ができたので、今回の記事ではRailsの方でもユーザーの認証を行っていきます。

前回の記事はこちら: Nuxt + Rails + Firebase ユーザー登録 (Nuxt編)

今回使用するgemはfirebase-auth-railsです。

https://github.com/penguinwokrs/firebase-auth-rails

このgemを使用するとことでfirebaseと通信を行うことができ認証情報を取得できるのdeviseで使用していたcurrent_userのヘルパーメソッドを使用できます。

またこのgemは内部でfirebase_id_tokenというgemを使用していてそのgemではRedisを使っているのでそのセットアップ方法も記述しています。

早速、gemのREADMEに沿って進めていきます。

Railsセットアップ

1. gemのインストール

api/Gemfile

gem 'firebase-auth-rails' # 追記

Gemfileに記述できたらインストールします。

bundle install

2. ユーザーモデルの作成

続いてユーザーモデルを作成します。

api $ rails g model user

ファイルが作成できたらマイグレーションファイルを修正します。

api/db/migrate/xxxx_create_users.rb

class CreateUsers < ActiveRecord::Migration[6.1]
  def change
    create_table :users do |t|
      t.string :name
      t.string :email
      t.string :uid, null: false

      t.timestamps
    end
  end
end

修正を反映させます。

api $ rails db migrate

3. initializers設定

initializerの設定を行っていきます。api/config/initializersディレクトリ下に firebase_auth_initializer.rbのファイルを追加してください。

api/config/initializers/firebase_auth_initializer.rb

FirebaseIdToken.configure do |config|
  config.redis = Redis.new
  config.project_ids = ['firebase_project_id']
end

ここのfirebase_project_idは自分のproject_idが入ります。

project_idは firebaseコンソール -> プロジェクトの概要の横にある歯車マー ク-> プロジェクトを設定 から確認できます。

4. モジュールインクルード

configの設定が完了したらモジュールをインクルードします。

api/app/controllers/application_controller.rb

class ApplicationController < ActionController::API
  include Firebase::Auth::Authenticable
end

application_controlerにモジュールをインクルードしたのでこのコントローラを継承してるコントローラでAuthenticableのモジュールが使用できます。。

ユーザーがログインしているかチェックしたいところに before_action :authenticate_user を記述すればdeviseと同じようにユーザーのチェックと current_user を取得できます。

5. ユーザー登録処理

続いてユーザーの登録処理を実装していきます。

ルーティング

api/config/routes.rb

Rails.application.routes.draw do
  namespace :api do
    namespace :v1 do
      namespace 'users' do
        resources :registrations, only: [:create]
      end
## 省略

コントローラ

api/app/controllers/api/v1/users/registrations_controller.rb

module Api
  module V1
    module Users
      class RegistrationsController < ApplicationController
        def create
          raise ArgumentError, 'BadRequest Parameter' if payload.blank?
          user = User.create!(sign_up_params.merge(uid: payload['sub']))
          render json: user, status: :ok
        end
    
        private
    
        def sign_up_params
          params.require(:registration).permit(:email)
        end
    
        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/users/registrations のPOSTリクエストの際にトークンをチェックしてトークンが正しければユーザーを作成し、トークンが正しくなかったらエラーを返す処理を行っています。

ひとまずRails側のfirebaseのトークン認証からユーザー作成までの実装は完了しました。

Nuxt 登録処理修正

Rails側のユーザー登録処理が実装できたのでNuxt側で新規登録した際に /api/v1/users/registrations のPOSTリクエストを送信するよう記述を修正していきます。

methods: {
    signup() {
      firebase.auth().createUserWithEmailAndPassword(this.email, this.password)
        .then((res) => {
          const params = { token: res.user.za, registration: { email: res.user.email } }
          const url = '/api/v1/users/registrations'
          this.$axios.post(url, params)
            .then((res) => {
              console.log(res)
              this.$router.push('/')
            })
            .catch((error) => {
              // TODO: ユーザー作成に失敗した場合の処理追加
              console.log(error)
            })
        })

createUserWithEmailAndPassword() の処理に成功したレスポンスの中のuserの中のzaというキーがトークンにあたるのでそれとメールアドレスをパラメータとしてrails側に送ります。

Nuxt側もこれで実装が完了したので実際に新規登録してみるのですが、下記のようなエラーが返ってきました。

Redis::CannotConnectError (Error connecting to Redis on 127.0.0.1:6379 (Errno::ECONNREFUSED)):

調べてみると以下のようなことがわかりました。

内部でfirebase_id_tokenというgemを使っていて、このgemはRedisが必須のためインストールしておきます。 google x509 証明書とその期限を管理するためにRedisを使っているようです。

https://simple-minds-think-alike.hatenablog.com/entry/rails-firebase-authentication

Redisの設定がまだなのでエラーが返ってきたみたいです。なのでRedisの設定を行っていきます。

Redis設定

homebrew で redis をインストールします。

api $ brew install redis

redis がインストールされているかは下記コマンドで確認できます。

$ which redis-server
/usr/local/bin/redis-server  <= インストールできている場合このように表示されます。

redis を起動します。

api $ redis-server

FirebaseIdToken のトークン確認をするにはリクエストが必要なのでコンソールからリクエストを送信します。

api $ rails c
> FirebaseIdToken::Certificates.request
> FirebaseIdToken::Certificates.present?
=> true

FirebaseIdToken::Certificates.present?でtrueが返ってきたらトークン確認が行えるようになっています。

もう一度ユーザー登録を行います。

下記画像のようにレスポンスが返ってきたら成功です。

以上で「Nuxt側でユーザー新規登録をした時にRails側でもユーザーを追加」の実装は完了です。

図にすると以下のような順序で登録処理が行われています。

Nuxt Headers情報追加

最後にcurrent_user情報を取得するためにリクエストのヘッダー情報にトークンを追加します。

ドキュメントには以下のように記述して追加できると書いています。

// Adds header: `Authorization: Bearer 123` to all requests
this.$axios.setToken('123', 'Bearer')

authentication.jsのplugins内でユーザーの情報が取得できたらヘッダーにトークン情報をセットします。

front/plugins/authentication.js

import firebase from '~/plugins/firebase'

export default function ({ $axios, route, redirect, store }) {
  firebase.auth().onAuthStateChanged(function(user) {
    if (user) {
      // User is signed in.
      store.commit('user/login')
      const token = user.za
      $axios.setToken(token, 'Bearer')
    } else {
      // No user is signed in.
      if (route.path !== '/login') {
        redirect('/login')
      }
    }
  });
}

storeの中身はこのようになっています。

front/store/user.js

export const state = () => ({
  loggedIn: false,
})

export const mutations = {
  login(state) {
    state.loggedIn = true
  },
}

これでユーザー認証されていたら全てのリクエストのヘッダーにこのトークン情報が付与されるのでこのトークン情報をRails側で受け取ってfirebaseと認証してcurrent_userを取得できるようになりました。

しかし今のまま /posts にアクセスするとNuxtのfirebase認証より postsのGETリクエストの方が早く実行されるRails側ではトークン情報がない状態になってしまいます。

なのでstoreでログイン状況を管理してログインが確認できたタイミングでGETリクエストを送るように修正します。

front/pages/posts/index.vue 

export default {
  computed: {
    loggedIn() {
      return this.$store.state.user.loggedIn
    },
  },

  methods: {
    fetchContents() {
      console.log('posts')
      const url = "/api/v1/posts"
      this.$axios.get(url)
        .then((res) => {
          this.posts = res.data.posts
        })
    },
  },

  watch: {
    loggedIn: function() {
      if (this.loggedIn) {
        this.fetchContents()
      }
    }
  },
}

これでユーザーの認証が完了してから/api/v1/postsのGETリクエストが実行されるようになったのでRails側でcurrent_userが取得できるか確認してみます。

indexアクションにbinding.pryを追加します。

api/app/controllers/api/v1/posts_controller.rb

module Api
  module V1
    class PostsController < ApplicationController
      before_action :authenticate_user

      def index
        binding.pry
        @posts = Post.all.order(updated_at: :desc)
      end

追加できたらhttp://localhost:8080/posts にアクセスしてコンソールの方を止めてでcurrent_userと入力します。

current_userが取得できました。

以上でNuxt + Rails + Firebaseのユーザー認証の実装は終了です。

最後までご覧いただきありがとうございました。

Rails + Nuxt + Firebaseカテゴリの最新記事