Next.js、Rails とFirebase AuthenticationでJWT認証を実装する

#はじめに
JWT認証に関して、Next.js Rails Firebase Authenticationという組み合わせでの実装をした記事があまりないとのことなので、今回は実際にやっていこうと思う。
ちなみに、今回は
https://simple-minds-think-alike.hatenablog.com/entry/rails-firebase-authentication
を参考にしながらやっていく。

実際のファイル構成等は下記から閲覧していただけると幸いです。
（まだアプリ自体は作成中なので悪しからず・・・）
https://github.com/yumaasato/m-api
https://github.com/yumaasato/m-frontend

##環境
Ruby: 2.6.5
Rails 6.0.3.6
firebase-auth-rails
Redis

*ちなみに今回、user認証でよく用いられるdeviseは必須ではありません。
今回は使わずに実装を進めていきます。


##1. Redisをインストールする
mac版の場合は、

```
$ brew install redis-server
```

Linuxでは、

```
$ sudo apt install redis-server
```

ですかね。
内部で[firebase_id_token](https://github.com/fschuindt/firebase_id_token)を使っているためこのRedisをインストールする必要があるとのことです。

##2. firebase-auth-railsの追加
今回、[firebase-auth-rails](https://github.com/penguinwokrs/firebase-auth-rails)というgemを利用することでjwt認証を比較的簡単に行うことができます。

```rails:Gemfile
gem 'firebase-auth-rails'
```
を追加します。

そして、

```
$ bundle install
```
を実行します。

##3-1. 実装（プロジェクトの設定）
まずはじめに、Reidsとfirebaseプロジェクトの設定をおこないます。

```rails:config/initializers/firebase_id_token.rb
FirebaseIdToken.configure do |config|
  config.redis = Redis.new
  config.project_ids = [ENV['FIREBASE_PROJECT_ID']]
end
```

##3-2. uidを追加
firebaseのUIDを保存するカラムを追加します。

```
$ rails g migration AddUidToUsers uid:string
```
その後、

```
$ rails db:migrate
```
を実行する。

3-3. Application Controller
```api/v1```側にApplication Controllerを

```rails:app/controllers/api/v1/application_controller.rb
module Api
  class V1::ApplicationController < ActionController::API
    include Firebase::Auth::Authenticable
    before_action :authenticate_user
  end
end
```
としており、元のApplication Controllerについては触れられていないが、
私は下記のようにした
```rails:app/controllers/application_controller.rb

```
 class ApplicationController < ActionController::Base

 end
```
つまり、```application_controller.rb```を２つ書いたというわけだが、これで正しいのかは正直怪しい。
しかし、しっかり実装できているため、一つの参考にはなるだろう。

##3-3. ユーザー登録用のコントローラー実装
次に、ユーザー登録用のコントローラー実装する。正直名前は何でもよいが、今回は```users_controller.rb```とする。

```rails:app/controllers/pi/v1/auth/users_controller.rb
require_dependency 'api/v1/application_controller'
module Api
  module V1
    module Auth
      class UsersController < 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']) do |user|
            user.name = payload['name']
          end
          if @user.save
            render json: @user, status: :ok
          else
            render json: @user.errors, status: :unprocessable_entity
          end
        end

        private

        def token
          params[:token] || token_from_request_headers
        end

        def payload
          @payload ||= FirebaseIdToken::Signature.verify token
        end
      end
    end
  end
end
```
今回は、Userのnameにnull:falseと設定しているため、

```
 @user = User.find_or_initialize_by(uid: payload['sub']) do |user|
            user.name = payload['name']
          end
```
という記述になっている。
そうでない場合は、参考記事通りに
``` @user = User.find_or_initialize_by(uid: payload['sub']) ```
でもよいかと思われる。

##3-4. Routeの追加
私は、下記の通りに実装した。

```rails:app/config/routes.rb
Rails.application.routes.draw do
  namespace 'api' do
    namespace 'v1' do
      resources :players
      resources :games, only: %i(index)
        namespace 'auth' do
          post 'users' => 'users#create'
        end
    end
  end
end
```

##4-1. 実装後のオペレーション（重要）
実装後、```$ rails c ```として、[firebase_id_tokenのDownloading Certificates](https://github.com/fschuindt/firebase_id_token)以下に記載してある内容を実行する必要がある。

```
irb(main):001:0>FirebaseIdToken::Certificates.request
・
・
・
irb(main):002:0>FirebaseIdToken::Certificates.present?
=> true
```
となってはじめて、jwt認証を使うことができる。

##5. Next側の実装（補足）
次にNext.js側の実装を行う。フロント側の実装方法が省かれている記事が多いが、ここではNext側についても実装方法を記述する。
詳しい記述方法は　https://github.com/yumaasato/m-frontend　
を参照していただきたい。
あくまで参考だが、```pages/login/index.tsx```で

```
export default function LoginPage() => {
  const [user, loading] = useAuthState(getAuth());
  const router = useRouter();
　　・
　　・

　　// google認証
  const signInGoogle = async () => {
    await auth.signInWithPopup(provider).catch((err) => alert(err.message));
    router.push('/')
  };

  const handleGoogleLogin = () => {
    const request = async () => {
      const user = await signInGoogle();
      const auth = getAuth();
      const currentUser = auth.currentUser;
      if (auth && auth.currentUser) {
        const token = await currentUser.getIdToken(true);
        const config = { token };
        try {
          await axios.post('/api/v1/auth/users', config);
        } catch (error) {
          console.log(error);
        }
      }
    };
    request();
  };
}

return (
    <div className='w-2/3 py-20 mx-auto'>
      <p className='font-bold text-4xl py-5 text-center'>Login</p>
      <Button
        fullWidth
        variant="contained"
        color="default"
        className={classes.submit}
        startIcon={<CameraIcon />}
        onClick={handleGoogleLogin}
      >
        SignIn with Google
            </Button>
    </div>
  );

```
とすればいいのではないか。
これは、```signInGoogle```でfirebase AuthでのGoogle認証を行い、
```handleGoogleLogin```でRails側にAPIのデータを送るというものである。
ちなみに、ここでは
```axios.defaults.baseURL ='http://localhost:3000'```
としている。

これで一通りの実装は終えたことになる。

###補足
Railsにおいて、```.env```ファイルで環境変数を管理する際には、

```
gem 'dotenv-rails'
```
をインストールする必要があるため、忘れずに設定しておこう。
（これで認証に時間がかかったので・・・）

##おわりに
エンジニアの方からすれば、この認証は大したことはないのかもしれないが、記事を参考にしながら実装を進めている初学者からすれば、なかなか骨の折れる作業である。事実、私もこの実装だけでかなりの時間を費やしてしまった。

私と同じようにNext.js、Rails とFirebase AuthenticationでのJWT認証で困っている人たちの助けになれば幸いである。また、どこか誤りがありましたら、伝えてもらえると助かります。最後まで閲覧していただきまして、誠にありがとうございました。