Railsチュートリアルのremember meの永続化Cookieの実装を参考にする。
「匿名認証」とは
ここでは、
- ユーザー登録・ログイン不要
- ブラウザのDBを利用してユーザーを判別できるようにする
という機能を意味する。
「匿名認証」の呼び方はFirebaseのAnonymous Authentication
に倣う。
環境
実装
まず、gem 'bcrypt'
を追加する。
Userモデルの作成
rails g model user user_digest:string
でモデルとテーブルを作成する。
1
2
3
4
5
6
7
8
9
|
class CreateUsers < ActiveRecord::Migration[6.0]
def change
create_table :users do |t|
t.string :user_digest, null: false
t.timestamps
end
end
end
|
user_digest
は、ブラウザからログイン中のユーザーを識別するためのカラムになる。
ユーザー作成処理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
class User < ApplicationRecord
attr_accessor :uuid
class << self
def create_anonymous!
uuid = new_uuid
user = new(
uuid: uuid,
user_digest: digest(uuid)
)
user.save!
user
end
private
def new_uuid
SecureRandom.urlsafe_base64
end
def digest(string)
cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST : BCrypt::Engine.cost
BCrypt::Password.create(string, cost: cost)
end
end
end
|
attr_accessor :uuid
は、Userのインスタンスでは保持するが、DBでは保持しない値である。
user = User.new(uuid: 'ゆーゆーあいでぃー')
ならuser.uuid => "ゆーゆーあいでぃー"
だが、
user = User.first
などDBから読み込んだ値だとuser.uuid => nil
になる。
1
2
3
4
5
6
7
8
9
|
class ApplicationController < ActionController::Base
before_action :set_user
private
def set_user
User.create_anonymous!
end
end
|
ApplicationControllerのbefore_actionなので、全アクションの最初にset_user
が実行される。
ログイン処理
上記のコードだと、ユーザーがアクセスするたびに新規Userが作成されるだけなので、Userを作成した後にログイン(匿名認証)できるようにする。
- 初回アクセス時
- User作成
- cookieにuser.idとuser.uuidを入れる(uuidが鍵になる)
- 二度目以降
- cookieのuser_idとuuidから既存ユーザーを探索してログインする
つまり、初回に作成したランダムなuuidをブラウザ側で保持し、DBに保存したuser_digest(uuidをハッシュ化した値)と突き合わせることでログインする。
1
2
3
4
5
6
7
8
9
10
|
class User < ApplicationRecord
attr_accessor :uuid
# クラスメソッドを省略
# 下記を追加
def authenticated?(uuid)
BCrypt::Password.new(user_digest) == uuid
end
end
|
1
2
3
4
5
6
7
8
9
10
11
12
13
|
module SessionsHelper
def remember(user)
cookies.permanent.signed[:user_id] = user.id
cookies.permanent[:uuid] = user.uuid
end
def current_user
if (user_id = cookies.signed[:user_id])
user = User.find_by(id: user_id)
@current_user ||= user if user && user.authenticated?(cookies[:uuid])
end
end
end
|
current_user
は、ログイン状態だと該当のUserインスタンス、ログインしていない状態(初回アクセス時)だとnil
を返す。
なので、以下の分岐を入れる。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
class ApplicationController < ActionController::Base
include SessionsHelper
before_action :set_user
private
def set_user
# この分岐を追加する
unless current_user
user = User.create_anonymous!
remember(user)
end
end
end
|
補足
current_user
のコードについて
Railsチュートリアルに従うと、current_user
の中身は以下のようにしている。
しかし、今回は通常ログイン用のsessionが不要なので、cookieだけで判別する。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
module SessionsHelper
def log_in(user)
session[:user_id] = user.id
end
def current_user
if (user_id = session[:user_id])
@current_user ||= User.find_by(id: user_id)
elsif (user_id = cookies.signed[:user_id])
user = User.find_by(id: user_id)
if user && user.authenticated?(cookies[:uuid])
log_in(user)
@current_user = user
end
end
end
end
|
Firebaseとの違い
Firebaseの匿名認証では、IndexedDBにユーザーのuuidを格納している。
参考