1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Cookieを利用した自動ログイン

Last updated at Posted at 2022-03-06

はじめに

Ruby on Rails チュートリアルで学んだCookieを利用した自動ログイン
備忘録

ブラウザを閉じた後でもログインを有効にするRemember meを実装する
Ruby on Railsを学んでいる方・学ぶ予定の方
前提の記事を読んだ後、こちらを読んでいただければ教材を進める助けになると思います。

前提

自動ログインのない基本的なログイン機構を実装していることが前提です
上記記事でログイン機能の実装を把握した後にこちらを読んでください。
どちらもRuby on Railsチュートリアルのコードを利用しています。

素敵な記事をお借りします。

remember_digest属性をUserモデルに追加

# 語尾の_to_usersはmigration対象がuses_tableだと明示
$ rails generate migration add_remember_digest_to_users remember_digest:string

$ rails db:migrate

メソッドを追加する

Userモデル

attr_accessor :remember_token # 仮想カラムを用意する

# 渡された文字列のハッシュ値を返す
def self.digest(string)
  cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST :
                                                BCrypt::Engine.cost
  BCrypt::Password.create(string, cost: cost)
end

# ランダムなトークンを返す
def self.new_token
  SecureRandom.urlsafe_base64 # 標準のmodele
end

# 永続セッションのためにユーザーをデータベースに記憶する
def remember
  self.remember_token = User.new_token
  update_attribute(:remember_digest, User.digest(remember_token))
end

# 渡されたトークンがダイジェストと一致したらtrueを返す
def authenticated?(remember_token)
  return false if remember_digest.nil?
  BCrypt::Password.new(remember_digest).is_password?(remember_token)
end

# ユーザーのログイン情報を破棄する
def forget
  update_attribute(:remember_digest, nil)
end

helperを定義する

app/helpers/sessions_helper.rb

# ユーザーのセッションを永続的にする
def remember(user)
  user.remember
  cookies.permanent.signed[:user_id] = user.id
  cookies.permanent[:remember_token] = user.remember_token
end

ログインと連動させる

app/controllers/sessions_controller.rb

def create
.
.
  remember user
.
end

current_userを更新する

ログインと連携させた時点でcookiesには有効なidとtokenが保存されたが、保存したcookiesを使用する記述をしていない
有効なcookiesがあれば自動ログインできるようにする
app/helpers/sessions_helper.rb

# 記憶トークンcookieに対応するユーザーを返す
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[:remember_token])
      log_in user
      @current_user = user
    end
  end
end

ユーザーを忘れる

forget(user)ヘルパーメソッドを作成する
log_outからfoget(user)を呼び出す

app/helpers/sessions_helper.rb

# 永続的セッションを破棄する
  def forget(user)
    user.forget # forgetはmodelのインスタンスメソッド
    cookies.delete(:user_id)
    cookies.delete(:remember_token)
  end

  # 現在のユーザーをログアウトする
  def log_out
    forget(current_user) # これを追記
    session.delete(:user_id)
    @current_user = nil
  end

小さなバグに対応する

2つのタブで2回ログアウトを実行した際forget(current_user)が失敗する

2種類のブラウザで利用した際、片方だけログアウトして、もう片方で再度訪れると、remember_digestnilなのにcookiesで自動ログインしようとして、Bcryptのtokenとremember_digestの比較で例外が発生する

ログインしている時だけログアウト処理を実行する

app/controllers/sessions_controller.rb

def destroy
  log_out if logged_in? # ここを編集
  redirect_to root_url
end

記憶ダイジェストがnilの場合にfalseを返す

app/models/user.rb

# 渡されたトークンがダイジェストと一致したらtrueを返す
def authenticated?(remember_token)
  return false if remember_digest.nil? # これを追記した
  BCrypt::Password.new(remember_digest).is_password?(remember_token)
end

チェックボックで永続的ログインを選択する

ログインのフォームに下記を追記する

# labelの内側に配置する配置する
<%= f.label :remember_me, class: "checkbox inline" do %>
  <%= f.check_box :remember_me %>
  <span>Remember me on this computer</span>
<% end %>

チェックボックスの仕様
ONの時 '1'になり、OFの時 '0'になる

createアクションを編集する

def create
  .
  .
  # チェックが入っている時のみtokenを記憶する 三項演算子
  params[:session][:remember_me] == '1' ? remember(user) : forget(user)
  .
end

終わりに

Ruby on RailsチュートリアルはRailsの使い方というより、Webアプリケーションの作り方を体系的に学べます。
ログイン機構の仕組みは他の言語やフレームワークを使った場合でも同じなので、書き方ではなくどういう仕組みなのかを理解したいですね。

1
0
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?