Rails Tutorial 第9章

## 演習1

>コンソールを開き、データベースにある最初のユーザーを変数userに設定してください。その後、そのuserオブジェクトからrememberメソッドがうまく動くかどうか確認してみましょう。また、remember_tokenとremember_digestの違いも確認してみてください。

```
user = User.first

user.remember
  TRANSACTION (0.1ms)  begin transaction
  User Update (0.3ms)  UPDATE "users" SET "updated_at" = ?, "remember_digest" = ? WHERE "users"."id" = ?  [["updated_at", "2023-09-23 21:17:06.020109"], ["remember_digest", "$2a$12$9LYztWLrADgJfw8zPs6QmOAXDbh0F4UwJHzSGylAGsfS4WMZ.EbIu"], ["id", 1]]
  TRANSACTION (30.7ms)  commit transaction
=> true

user.remember_token
=> "8JfT4yX-tQJk3dF2VrxOvA"

user.remember_digest
=> "$2a$12$9LYztWLrADgJfw8zPs6QmOAXDbh0F4UwJHzSGylAGsfS4WMZ.EbIu"


```



>リスト 9.3では、明示的にUserクラスを呼び出すことで、新しいトークンやダイジェスト用のクラスメソッドを定義しました。実際、User.new_tokenやUser.digestを使って呼び出せるようになったので、おそらく最も明確なクラスメソッドの定義方法であると言えるでしょう。しかし実は、より「Ruby的に正しい」クラスメソッドの定義方法が2通りあります。1つはややわかりにくく、もう1つは非常に混乱するでしょう。テストスイートを実行して、ややわかりにくいリスト 9.4の実装でも、非常に混乱しやすいリスト 9.5の実装でも、いずれも正しく動くことを確認してみてください。（ヒント: selfは、通常の文脈ではUser「モデル」、つまりユーザーオブジェクトのインスタンスを指しますが、リスト 9.4やリスト 9.5の文脈では、selfはUser「クラス」を指すことにご注意ください。わかりにくさの原因の一部はこの点にあります。）

リストで示された2つのコードを挿入してrails testをしてみましょう。どちらもGREENになります

## 演習2

>ブラウザのcookieを調べ、ログイン後のブラウザではremember_tokenと暗号化されたuser_idがあることを確認してみましょう。

検証ツールでのcookieの調べ方は以下をご参照ください。
https://qiita.com/ryoheitakahashi/items/412efdf6be506d3ad74e#%E6%BC%94%E7%BF%925

検証ツールのアプリケーションを開き、cookieを調べてみましょう
![スクリーンショット 2023-09-24 065558.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3451993/7b9e2db9-ad48-21d0-e0c6-0dc8d0786c46.png)

user_idとremember_token

>コンソールを開き、リスト 9.6のauthenticated?メソッドがうまく動くかどうか確かめてみましょう。

```
user = User.first

user.remember
  TRANSACTION (0.1ms)  begin transaction
  User Update (0.3ms)  UPDATE "users" SET "updated_at" = ?, "remember_digest" = ? WHERE "users"."id" = ?  [["updated_at", "2023-09-23 22:07:36.189636"], ["remember_digest", "$2a$12$idwygaloKNept52YMYun6OqClMcWjiQ8qHobKyhmljCVbjngxxfvm"], ["id", 1]]
  TRANSACTION (27.4ms)  commit transaction
=> true

 user.authenticated?(user.remember_token)
=> true
```

## 演習3

>ログアウトした後に、ブラウザの対応するcookiesが削除されていることを確認してみましょう。

![スクリーンショット 2023-09-24 072329.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3451993/22517956-d47a-f9be-df3d-90a1f1fa436b.png)

ログアウト後、user_idとremember_tokenが消えていることを確認します

## 演習4
>リスト 9.17で修正した行をコメントアウトし、2つのログイン済みのタブによるバグを実際に確かめてみましょう。まず片方のタブでログアウトし、その後、もう1つのタブで再度ログアウトを試してみてください。

まずはログイン済みのタブを2つ用意しましょう
chromeであれば、タブを複製で2つのログイン済みのページを作れます
![スクリーンショット 2023-09-25 043318.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3451993/0a659685-f9cf-0c95-689b-a281d129d14a.png)

そして指示通り片方のタブでログアウトして、もう1つのタブでログアウトをしてみましょう
![スクリーンショット 2023-09-25 043526.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3451993/3567c9bf-7fd9-c245-b934-92d6b43c08af.png)

ログアウトしたにも関わらず、ドロップダウンメニューはlog outボタンのままですね

>リスト 9.20で修正した行をコメントアウトし、2つのログイン済みのブラウザによるバグを実際に確かめてみましょう。まず片方のブラウザでログアウトし、もう一方のブラウザを再起動してサンプルアプリケーションにアクセスしてみてください。

![スクリーンショット 2023-09-25 044518.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3451993/807b8500-0a7f-616d-a165-0c9ef6a91fdf.png)

今度はログイン済みにも関わらずログインメニューが出てしまっていますね

>上のコードでコメントアウトした部分を元に戻し、テストスイートが red から green になることを確認しましょう。

コメントアウトを元に戻し、rails testを実行して確認しましょう。

## 演習5
>ブラウザでcookies情報を調べ、［remember me］をチェックしたときに意図した結果になっているかどうかを確認してみましょう。


まずはrember meのチェックボックスを付けてログインし、検証ツールでcookieを確認してみましょう

![1回目、remember.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3451993/d647e501-7d31-fe5e-a420-912d93371b69.png)

続いて一度ブラウザを閉じて、ログインされた状態であることを確認します
![2回目 remember.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3451993/2e8589af-0c76-482b-447e-f18eb55326ba.png)

remember_tokenとuser_idが同じなら、意図した結果になっています

>コンソールを開き、三項演算子を使った実例を考えてみてください（コラム 9.2）。

```
name = "髙橋"
=> "髙橋"
name.empty? ? 'empty' : 'not_empty'
=> "not_empty"
```

## 演習6
>リスト 9.26の統合テストでは、仮想のremember_token属性にアクセスできないと説明しましたが、実は、assignsという特殊なテストメソッドを使うとアクセスできるようになります。コントローラで定義したインスタンス変数にテストの内部からアクセスするには、テスト内部でassignsメソッドを使います。このメソッドにはインスタンス変数に対応するシンボルを渡します。例えばcreateアクションで@userというインスタンス変数が定義されていれば、テスト内部ではassigns(:user)と書くことでインスタンス変数にアクセスできます。本チュートリアルのアプリケーションの場合、Sessionsコントローラのcreateアクションでは、userをインスタンス変数ではない通常のローカル変数として定義しましたが、これをインスタンス変数に変えてしまえば、cookiesにユーザーの記憶トークンが正しく含まれているかどうかをテストできるようになります。このアイデアに従ってリスト 9.29とリスト 9.30の不足分を埋め、［remember me］チェックボックスのテストを改良してみてください。ヒントとして?や（コードを書き込む）を目印に置いてあります。なお、この演習問題で行う変更はこの後のリスト 10.33に影響するのでご注意ください。

リスト9.29　？の部分を@に変えます
```
class SessionsController < ApplicationController
  def new
  end

  def create
    @user = User.find_by(email: params[:session][:email].downcase)
    if @user && @user.authenticate(params[:session][:password])
      log_in @user
      params[:session][:remember_me] == '1' ? remember(@user) : forget(@user)
      redirect_to @user
    else
      flash.now[:danger] = 'Invalid email/password combination' 
      render 'new'
    end
  end

  def destroy
    log_out if logged_in?
    redirect_to root_url, status: :see_other
  end
end

```

リスト9.30の部分のコードを書き込みます
```

class RememberingTest < UsersLogin

  test "login with remembering" do
    log_in_as(@user, remember_me: '1')
#ここ
    assert_equal cookies['remember_token'], assigns(:user).remember_token
  end

  test "login without remembering" do
    # Cookieを保存してログイン
    log_in_as(@user, remember_me: '1')
    # Cookieが削除されていることを検証してからログイン
    log_in_as(@user, remember_me: '0')
    assert cookies[:remember_token].blank?
    assert_empty cookies[:remember_token]
  end
end
```


## 演習7

>リスト 9.35にあるauthenticated?の式を削除すると、リスト 9.33の2つ目のテストで失敗すること、つまりこのテストが正しい対象をテストしていることを確認してみましょう。

指定された式をコメントアウトし、rails testをしてみましょう

> 9.1.1で説明したように、私たちのアプリケーションのセッション内にあるuser_idは、このままだとセッションリプレイ攻撃に対して脆弱です。リスト 9.37のコードを用いて、リスト 9.38のコードの足りない部分「（コードを書き込む）」を埋めることで、コードを改善してください。また、以下のヒントも参考にしてください。
sessionハッシュがユーザーのセッショントークンと等しい場合のみ、@current_userを設定します。ただし、remember_digestは既に一意の値になっており、各ユーザーと紐付けられているので、単にこれをセッショントークンとして再利用し、ユーザーがまだ存在しない場合は生成することにします。このとき、rememberメソッドに少し手を加えて、update_attributeの結果を返すのではなく、リスト 9.37のようにダイジェストを返すようにする必要があります。
bcryptダイジェストの設計はセキュアであり、セッションの値は常に暗号化されるので、記憶ダイジェストを2つの目的に利用しても安全です。この実装では、ハイジャックされたセッションを無効にするために必要なのは、user.forgetを呼び出すことだけです。この後の演習（12.3.2.1）では、この手法を適用することで、ユーザーのパスワードをリセットするときに既存のセッションをすべて失効させられるようにする予定です。
最後に、これはセキュリティに関連する重要なコードなので、本チュートリアルの他の解答と異なり、以後のコードすべて、およびサンプルアプリにも必ず反映されています。

app/helpers/sessions_helper.rb
```
def current_user
    if (user_id = session[:user_id])
      user = User.find_by(id: user_id)
      if user && session[:session_token] == user.session_token
        @current_user = user
      end
      @current_user ||= User.find_by(id: user_id)
    elsif (user_id = cookies.encrypted[: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
```