##はじめに
JWT認証に関して、Next.js Rails Firebase Authenticationという組み合わせでの実装をした記事があまりないとのことなので、今回は実際にやっていこうと思う。今回はEmailでの実装になるため、Google認証で実装をしたいという方は同シリーズの
https://qiita.com/satopin/items/fa0c35a0ba69a379683e
をご覧いただければ幸いである。
ソースコードはこちらから
https://github.com/yumaasato/my-mahjong
(まだアプリ自体は作成中なので悪しからず・・・)
環境
Ruby: 2.6.5
Rails 6.0.3.6
TypeScript
firebase-auth-rails
Redis-server
*ちなみに今回、user認証でよく用いられるdeviseは必須ではありません。
今回は使わずに実装を進めていきます。
また、今回redis-serverのインストールからconfig/initializers/firebase_id_token.rbで設定を行うまでは
https://qiita.com/satopin/items/fa0c35a0ba69a379683e
と同じのため、3-1までは飛ばしてもらって構わない。
##1. Redis-serverのインストール
mac版の場合
$ brew install redis-server
Linuxでは
$ sudo apt install redis-server
内部でfirebase_id_tokenを使っているためこのRedisをインストールする必要があるとのことです。
##2. firebase-auth-railsの追加
今回、firebase-auth-railsというgemを利用することでjwt認証を比較的簡単に行うことができます。
gem 'firebase-auth-rails'
を追加します。
そして、
$ bundle install
を実行します。
##3-1. 実装(プロジェクトの設定)
まずはじめに、Reidsとfirebaseプロジェクトの設定をおこないます。
FirebaseIdToken.configure do |config|
config.redis = Redis.new
config.project_ids = [ENV['FIREBASE_PROJECT_ID']]
end
##3-2.Userテーブルの設計
今回は、Userのnameをクライアント側で登録せずにEmailとPasswordのみの認証を行う。
そのため、Userテーブルの編集を行う必要がある。
以下は一例である。
class CreateUsers < ActiveRecord::Migration[6.0]
def change
create_table :users do |t|
t.string :password_digest
t.string :name, default: "" # null: falseとnameが必須になるので今回は外す
t.string :email, default: ""
t.string :uid, null: false, default: ""
t.timestamps
t.index :uid, unique: true # 同じuidによる登録を防ぐ
end
end
end
ここで、uidを null:falseにしておくことで、Firebase Authで認証したUserをRails側で受け取れるようにしておく。
##3-3.ユーザー登録用のコントローラを実装する
以下はあくまで一例である
require_dependency 'api/v1/application_controller'
module Api
module V1
module Auth
class UsersController < V1::ApplicationController
skip_before_action :authenticate_user
def index
# ユーザー一覧を取り出す
users = User.order(created_at: :desc)
render json: { status: 'SUCCESS', message: 'Loaded users', data: users }
end
def create
# ユーザーを作成する
FirebaseIdToken::Certificates.request
raise ArgumentError, 'BadRequest Parameter' if payload.blank?
@user = User.find_or_initialize_by(uid: payload['sub']) do |user|
user.email = payload['email']
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
Google認証では、
@user = User.find_or_initialize_by(uid: payload['sub']) do |user|
user.name = payload['name']
end
としたが、これでは、payloadでUserのnameが要求される。今回Userのnameは使わずに、Emailのみの認証を行うため、
@user = User.find_or_initialize_by(uid: payload['sub']) do |user|
user.email = payload['email']
end
とする。ちなみに user.password= payload['password']
とする必要はない。Passwordを暗号化した文字列ならわかるが、データベースにpasswordを保管するのはセキュリティ的に問題があるからだ。
##3-4 Routeの追加
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' #追記箇所
get 'users' => 'users#index' #追記箇所
end
end
end
end
今回の場合、Firebase Authを介してUserの認証を行なっているため、上記のようにする。
ちなみに、get 'users' => 'users#index'
は、User一覧をみれるようにするために設定した。
##4-1. クライアント側の実装
次にNext.js側の実装を行う。フロント側の実装方法が省かれている記事が多いが、ここではNext側についても実装方法を記述する。あくまで参考(一部省略)だが、
// Eメール認証
const signUpEmail = async () => {
await auth.createUserWithEmailAndPassword(email, password).catch((err) => alert(err.message));
router.push('/')
};
// 認証後Rails側にリクエストを送る
const handleEmailsignUp = () => {
const request = async () => {
await signUpEmail();
const auth = getAuth();
const currentUser = auth.currentUser;
// Firebase Authの認証
if (auth && auth.currentUser) {
const token = await currentUser.getIdToken(true);
const config = { token };
// Rails側にリクエストを送る
try {
await axios.post('/api/v1/auth/users', config);
} catch (error) {
console.log(error);
}
}
};
request();
};
return (
<>
<TextField
variant="outlined"
margin="normal"
required
fullWidth
id="email"
label="Email Address"
name="email"
autoComplete="email"
value={email}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
setEmail(e.target.value);
}}
/>
<TextField
variant="outlined"
margin="normal"
required
fullWidth
name="password"
label="Password"
type="password"
id="password"
autoComplete="current-password"
value={password}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
setPassword(e.target.value);
}}
/>
<Button
disabled={
isLogin
? !email || password.length < 6
: !email || password.length < 6
}
fullWidth
variant="contained"
color="primary"
className={classes.submit}
startIcon={<EmailIcon />}
onClick={
isLogin
? async () => {
try {
await handleEmailLogin();
} catch (err) {
alert(err.message);
}
}
: async () => {
try {
await handleEmailsignUp();
} catch (err) {
alert(err.message);
}
}
}
>
{isLogin ? "Login" : "Register"}
</Button>
</>
)
これでButtononClick
##5-1. 実装後のオペレーション(重要)
実装後、$ rails c
として、firebase_id_tokenのDownloading Certificates以下に記載してある内容を実行する必要がある。
irb(main):001:0>FirebaseIdToken::Certificates.request
・
・
・
irb(main):002:0>FirebaseIdToken::Certificates.present?
=> true
となってはじめて、jwt認証を使うことができる。
注意点
この実装ではnameを使わずに実装をおこなったため、クライアント側でnameを入れて認証を行なった際には、エラーが起こる可能性がある。nameを入れて実装を進めたいという場合には、別の方法を参照してもらいたい。
また、Railsにおいて、.env
ファイルで環境変数を管理する際には、
gem 'dotenv-rails'
をインストールする必要があるため、忘れずに設定しておこう。
(これで認証に時間がかかったので・・・)
さらに、認証を行う際には、Railsサーバーとクライアント側のサーバーに加えて、$ redis-server
でRedis-serverを起動する必要があるため、忘れないようにしよう。
##終わりに
実装だけでもかなりの時間をかけた上に、Next.js(TypeScript) + Rails + Firebase Authの組み合わせでのjwt認証の記事が少ないということで今回記事を書くことになった。この記事が一人でも多くの人の参考になれば幸いである。