Help us understand the problem. What is going on with this article?

Rails チュートリアル 第9章 まとめ

全体像の把握

Cookiesはブラウザのみで保存する
new_tokenでランダムな値を生成し、それを仮想的な属性である.rememberと.remember_confirmationに一時的に保存し(合わせてremember_token)、その値をハッシュ化して、DBのremember_digestに保存。
さらに、remember_tokenはuser.idとともにcookiesにも焼き付ける。cookiesに置いておくことで、ユーザー情報を保持することができる。user_idを復号化することで、Userの情報を復元し、そこからDBのremember_digestを取得する。一方で、焼き付けておいたremember_tokenと照合して、認証を行う。

DBへdigestを保存

1 remember_tokenをハッシュ化させた値を保存しておく場所を作成
rails generate migration add_remember_digest_to_users remember_digest:string
rails db:migrate
2 ランダムな文字列を作成
SecureRandom.urlsafe_base64

3 仮想的な属性にアクセスする部分の実装(setterとgetter)
has_secure_passwordがやってくれていたことを自分で実装する必要がある
u.passwordに代入できるが、データベースに保存しないという機能(仮想的な属性にアクセス)
今はu.rememberとすることはできない

def remember= (token)
   @remember = token
end
def remember
   @remember
end

よく使うのでRubyで実装されており、この一行に短縮できる

attr_accessor :remember

4.保存

def remember
# Userがログイン状態を保持したいと思い、チェックボックスにチェック(remember me)し、
# ログインが成功した時に呼び出されるメソッドremember()から
    self.remember_token = User.new_token
# 新しいトークンを発行し、このメソッドを呼び出したオブジェクト(self)のremember_tokenに入れる
# remember_tokenは次のリクエストが来るまでしか有効ではない
    update_attribute(:remember_digest, User.digest(remember_token))
# new_tokenは開発側が作った値なので、これまでのようにvaliをかける必要がないので
# update_attribute()を使う
# update_attribute(name,value) = (属性名・カラム?,値・更新後の)
# remember_tokenがハッシュ化された値が、DBに保存
 end

認証

さらに認証のためにuserのid情報を暗号化したものもcookiesに入れておき、そこからuser_idを復号化し、find_byによりユーザーオブジェクトを取得し、athenticateでユーザーの平文(remember_token)とDBのdigestと照合する
authenticateでuser情報が必要な理由は、どのUserのremember_tokenか指定する必要があるから

bcryptの認証
remember_tokenとdigestが一致したらtrue
実際の認証の部分、図での一番最後の部分

def authenticated?(remember_token)
    BCrypt::Password.new(remember_digest).is_password?(remember_token)
end

このメソッドをsessionコントローラのcreateアクションのloginの後に書くことで、ログインと同時に、remember_tokenをDBへ保存したり、cookiesへ保存が行われる

sessions_helper
def remember(user)
    user.remember # DBへremember_digestを保存
    cookies.permanent.signed[:user_id] = user.id
# userのidも暗号化してcookiesに入れる
# 文法はあまり考えないでいい
    cookies.permanent[:remember_token] = user.remember_token
# remember_tokenをcookiesに入れる
end

sessionを使って復元することを試みて、sessionで無理なら、cookiesを使って復元する

def current_user
#sessionを使った復号化
 if user_id = session[:user_id] 
# user_idというローカル変数にsessionの値を代入
# 代入されたローカル変数の値を条件式で評価、比較演算子(==)ではない
# 評価されるので数字かnilが返る、数字ならtrue
      @current_user ||= User.find_by(id: user_id) # ここまでは同じ

#cookiesを使った復号化、cookiesが入ってなかったらnilで終了
 elsif (user_id = cookies.signed[:user_id])
# 復号化されて実際のuser_idになる
      user = User.find_by(id: user_id)
      # 数字が返ってきたら、user_idをfind_byで取得し、Userオブジェクトに引っ掛ける
      if user && user.authenticated?(cookies[:remember_token])
      #Userのcookiesにあるremember_tokenとremember_digestを照合
        log_in user #login状態に
        @current_user = user #User情報の復元
      end
 end
end

このメソッドをどこで呼び出しているのか、分かりづらいが
このメソッドは第8章でしたように、ここで呼び出される
使われているのは、_header.htmlで、ログインしている時としていない時の振る舞いを変えるところ

def logged_in?
    !current_user.nil?
end

第8章ではsessionが切れているかどうかでログインしているか確かめる意味合いでしかなかったが、ここではcookiesを使った復号化を試みる場所という機能も与えられている。
このlogged_inメソッドがheaderで使われているので、ユーザーがこのwebサイトの何らかのページに行く時には必ずcurrent_userメソッドも呼び出されると考えられる。
ログイン機構の理解
まずログイン時にセッションとクッキーでユーザー情報を保持する部分
次に、ユーザーがログアウトせずにページを離れて、またやってきた場合、そのユーザー情報を渡す(復号化)部分の構築がポイントだと思う

Userを忘れる
rememberメソッドと対になる

def forget
    update_attribute(:remember_digest, nil) #remember_digestをnilに
end

remember(user)の対

def forget(user)
    user.forget
    cookies.delete(:user_id)
    cookies.delete(:remember_token)
end
def log_out
    forget(current_user) #上のメソッドを呼んでいる
    session.delete(:user_id)
    @current_user = nil
end 

連続ログアウト問題
二つのデバイスでログインした状態で、片方をログアウトさせると、もう片方がログアウトする時にはlog_outメソッドのforget(current_user)のcurrent_userがいないのでnilが渡されErrorとなる

解決策として、log_outメソッドを呼び出す時、nilじゃないことを確認する
destroyアクションで
log_out if logged_in?
ログアウトするできるのはログインしている時だけ
2回目以降のログアウトはログインしていないので、log_outが実行されず、redirectだけ実行

二つのバグとlog_in_asのテストはよくわからない

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした