Railsチュートリアル第9章
はじめに
Ruby on Railsチュートリアル(第6版)のメモ、演習の解答例を記述した記事です。
解答は個人のものなので、誤りがあればご指摘ください。
開発環境 Ruby: 2.7.2 , Rails: 6.1.4
メモ
Remember me機能
ユーザーのログイン状態を、ブラウザを閉じたあとでも有効にする機能
sessionメソッドを利用したブラウザへのセッション情報は安全が保たれるが、ブラウザを閉じるセッション情報は消えてしまう
そこで、セッションの永続化をcookiesメソッドを利用し、安全性を加味しながら実現する
セッション
一時セッション :ブラウザを閉じると消えるセッションの事(sessionメソッドを利用)
永続的セッション:ブラウザを閉じても半永久的に消えないセッション(cookiesメソッドを利用)
cookieメソッドでの永続化によるデメリット
cookiesメソッドでは永続的にログインした状態が保たれるが、セッションハイジャック攻撃を受ける可能性がある。
セッションハイジャックの主な手法
- 管理の甘いネットワークを通過するネットワークパケットからパケットスニッファというソフトでcookieを直接取り出す
- データベースから記憶トークンを取り出す
- クロスサイトスクリプティングを使う
- ユーザがログインしているパソコンやスマホを直接操作してアクセス権を奪い取る
cookiesメソッドで状態を保持
sessionのようにハッシュとして扱う
cookiesは1つのvalueとオプションのexpires(有効期限)を持つ
記憶トークンをブラウザに記憶
cookies[:remember_token] = { value: remember_token, expires: 20.years.from_now.utc }
valueに記憶トークンをexpiresに有効期限を渡す。
次のコードも同じ処理をする
cookies.permanent[:remember_token] = remember_token
有効期限を20年とする設定がよく使われるため上記のようなpermanentというメソッドが追加された。
ユーザーIDをブラウザに記憶
signedメソッドで暗号化(署名付きcookie)、permanentメソッドで20年保持
cookies.permanent.signed[:user_id] = user.id
cookiesから暗号化されたユーザーIDを取り出す
cookies.signed[:user_id]
cookies.signed[:user_id]では自動的にcookiesの暗号化を解除し、
続いてbcryptを使ってcookies[:remember_token]がremember_digestと一致するかの確認をする
永続的セッションの削除
cookiesメソッドでブラウザのcookie情報を削除する
cookies.delete(:user_id) #ユーザーIDを削除 cookies.delete(:remember_token) #記憶トークンを削除
assignsメソッド
コントローラーのアクションが実行された後に、コントローラーで定義されたインスタンス変数を参照する。
9.3.1の演習1ではlogin_pathにPOSTリクエストを送りsession_controllerのcreateアクションが実行されたため、このcreateアクションで定義されたインスタンスメソッドを参照している。
raiseメソッド
プログラムの中で意図的に例外を発生させる
herokuメンテナンスモード
メンテナンスモードをオンにすることでページにアクセスできない状態にする。
メンテナンスモードをオン
$ heroku maintenance:on
メンテナンスモードをオフ
$ heroku maintenance:off
演習
9.1.1
演習1
コンソールを開き、データベースにある最初のユーザーを変数userに代入してください。その後、そのuserオブジェクトからrememberメソッドがうまく動くかどうか確認してみましょう。また、remember_tokenとremember_digestの違いも確認してみてください。
remember_digestにはremember_tokenをハッシュ化し値が保存されている。
irb(main):001:0> user = User.first (0.4ms) SELECT sqlite_version(*) User Load (0.1ms) SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT ? [["LIMIT", 1]] irb(main):002:0> user.remember User Update (0.3ms) UPDATE "users" SET "updated_at" = ?, "remember_digest" = ? WHERE "users"."id" = ? [["updated_at", "2021-07-11 08:36:08.652655"], ["remember_digest", "$2a$12$1VR./gDUj1Tt8XBN/SA3UO.TyXAKajeuN1868HOW7U1GsULWC9rX2"], ["id", 1]] TRANSACTION (0.6ms) commit transaction => true irb(main):003:0> user.remember_token => "AHgb7ct6KjACo4zSQ2ye2A" irb(main):004:0> user.remember_digest => "$2a$12$1VR./gDUj1Tt8XBN/SA3UO.TyXAKajeuN1868HOW7U1GsULWC9rX2"
9.1.2
演習2
コンソールを開き、リスト 9.6のauthenticated?メソッドがうまく動くかどうか確かめてみましょう。
irb(main):001:0> user = User.first (0.5ms) SELECT sqlite_version(*) User Load (0.1ms) SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT ? [["LIMIT", 1]] => #<User id: 1, name: "Rails Tutorial", email: "example@railstutorial.... irb(main):002:0> user.remember User Update (0.3ms) UPDATE "users" SET "updated_at" = ?, "remember_digest" = ? WHERE "users"."id" = ? [["updated_at", "2021-07-11 09:13:30.028775"], ["remember_digest", "$2a$12$YPaIMQRoOJV/a0Qp08e9Lu431JW2QKMu787PgwLVDfid8pHxfiy.a"], ["id", 1]] TRANSACTION (0.6ms) commit transaction => true irb(main):003:0> user.authenticated?(user.remember_token) => true
9.3.1
演習1
リスト 9.25の統合テストでは、仮想のremember_token属性にアクセスできないと説明しましたが、実は、assignsという特殊なテストメソッドを使うとアクセスできるようになります。コントローラで定義したインスタンス変数にテストの内部からアクセスするには、テスト内部でassignsメソッドを使います。このメソッドにはインスタンス変数に対応するシンボルを渡します。例えばcreateアクションで@userというインスタンス変数が定義されていれば、テスト内部ではassigns(:user)と書くことでインスタンス変数にアクセスできます。本チュートリアルのアプリケーションの場合、Sessionsコントローラのcreateアクションでは、userを(インスタンス変数ではない)通常のローカル変数として定義しましたが、これをインスタンス変数に変えてしまえば、cookiesにユーザーの記憶トークンが正しく含まれているかどうかをテストできるようになります。このアイデアに従ってリスト 9.27とリスト 9.28の不足分を埋め(ヒントとして?やFILL_INを目印に置いてあります)、[remember me]チェックボックスのテストを改良してみてください。
createアクション
def create @user = User.find_by(email: params[:session][:email].downcase) if @user&.authenticate(params[:session][:password]) log_in @user params[:session][:remember_me] == '1'? remember(@user) : forget(@user) redirect_to user_url(@user) else flash.now[:danger] = 'Invalid email/password combination' render 'new' end end
統合テスト
test "loin with remembering" do log_in_as(@user, remember_me: '1') assert_equal cookies[:remember_token], assigns(:user).remember_token end
おわりに
9章は内容が一気に難しくなった気がします。
処理が複雑になり、どう動いているか意識しながら進めないと混乱しますね、、