Railsチュートリアルのサインインの仕組みをまとめる


railsチュートリアルのサインインの部分を、自分用にまとめました。

[第8章 サインイン、サインアウト | Rails チュートリアル](http://railstutorial.jp/chapters/sign-in-sign-out?version=4.0#top)

### viewのレンダリングまでの大まかな流れ

`app/controllers/users_controller.rb`のnewメソッドを見てみます。この中には`@user = User.new`という記述しかありません。そして、規約に乗っ取り`app/view/users/new.html.erb`がレンダリングされます。ルーティングの流れは以上です。では、実際にUserが作られるところの設定は`app/model/user.rb`にあります。`User.new`を実行する時に自動で実行される処理を定義してあります。

### Active Recordのコールバックでモデル生成時に自動処理をさせる

`before_save`にてオブジェクト（モデル）が保存される際に、モデルのプロパティの`email`を小文字に変換します。そして、次に`before_create`が実行されます。ここではプライベートメソッド`create_remember_token`が実行されます。

`before_save`と`before_create`の違いですが、`before_save`はINSERTされる場合も、UPDATEされる場合も実行されます。一方、`before_create`はオブジェクトが登録される時にのみ実行され、更新される時は実行されません。UPDATEの時だけ実行せたい場合は、`before_update`に処理を定義します。INSERTかUPDATEかは`model_instance.new_record?`がtrueの時はINSERT、falseの時はUPDATEと判断されます。このようにある処理の前に呼び出される処理のことコールバックと言います。詳細は[Rails ガイド](http://railsguides.jp/active_record_callbacks.html#%E3%82%AA%E3%83%96%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%E3%81%AE%E6%9B%B4%E6%96%B0)で確認できます。

下記はモデルの操作時に実行されるコールバックの順番です。

**オブジェクトの作成・更新**

1.  before_validation
2.  after_validation
3.  before_save
4.  around_save

**オブジェクトの作成**

1.  before_create
2.  around_create
3.  after_create
4.  after_save

**オブジェクトの更新**

1.  before_update
2.  around_update
3.  after_update
4.  after_save

**オブジェクトの削除**

1.  before_destroy
2.  2around_destroy
3.  after_destroy

`remember_token`は`before_create`では保存してはいけません。ユーザーが最初に作成された時のみ保存することになると、セッションを盗まれた時にずっと使われる可能性があります。だから作成時にも更新時にもセッションを保存するために`before_save`で`remember_token`を保存しています。

### セッションを暗号化する

`before_create`で実行される、プライベートメソッド`create_remember_token`に話を戻します。このメソッドの処理はたったの1行。`self.remember_token = User.digest(User.new_remember_token)`だけです。

```ruby
# app/model/user.rb
# 一部省略
class User < ActiveRecord::Base
  before_save { self.email = email.downcase }
  before_create :create_remember_token
  has_secure_password
  validates :password, length: { minimum: 6 }

  def User.new_remember_token
    SecureRandom.urlsafe_base64
  end

  def User.digest(token)
    Digest::SHA1.hexdigest(token.to_s)
  end

  private

    def create_remember_token
      self.remember_token = User.digest(User.new_remember_token)
    end
end
```

`User.digest`はトークンからダイジェスト値を生成するもので、これに`User.new_remember_token`で生成したランダムな文字列をトークンとして渡しています。ダイジェスト値とはハッシュ値のことで、元のデータから生成された固定長の値です。元のデータが同じなら常に同じ値が作られます。また、元のデータの長さに関わらず、作られるダイジェスト値は同じです。これが固定長のことです。

ダイジェスト値の作成に使われる`Digest::SHA1.hexdigest`の特徴は次のとおりです。

* Bcryptアルゴリズムよりも動作がはるかに高速 →トークンの暗号化はユーザーがページを開くため
* SHA1はBcryptと比べるとそれほどセキュアではない →元となるトークンが既に16桁のランダムな文字列なので、これで十分

railsコンソールで実際に使って挙動を確認してみます。

```shell-session
$ rails c
irb(main):001:0> Digest::SHA1::hexdigest('token')
=> "ee977806d7286510da8b9a7492ba58e2484c0ecc"
irb(main):002:0> Digest::SHA1::hexdigest('token')
=> "ee977806d7286510da8b9a7492ba58e2484c0ecc"

irb(main):003:0> SecureRandom.urlsafe_base64
=> "563UsoRsWuhfjYDUa8I8HA"
irb(main):004:0> SecureRandom.urlsafe_base64
=> "vYIxeR4uaVOHPV-rZWfZrA"
```

### remember_tokenはどのように使われるのか？

DBへの保存は前述のとおりです。同じくブラウザのクッキーにも`remember_token`を保存するようにします。こうすればブラウザの値とDBの値を照らしあわせて、同じユーザーが閲覧を続けていることを証明することができます。これがないとページを遷移するごとに毎回パスワードを入力する必要があります。

### Sessionモデルは必要ない

Userモデルと違い、SessionはDBに保存せずにcocokieに保存するのでSessionモデルは必要ありません。そのためモデルではなく、コントローラーとして作成します。

### Sessionモデルがないのでエラーはflashで

`user.save`で`false`が返った時に、`user.errors.full_messages`でエラー内容を配列で受け取ることができます。これはActive Recordの機能で、Sessionモデルは作成していないので、エラーメッセージはflashに格納します。

### サインインとログインの維持

トークンを生成し、cookieとDBに保存します。そしてインスタンス属性にログインしたユーザーを保存します。これがログインです。ページ遷移後にログインを維持するには、cookieのトークンでユーザーを検索してカレントユーザーとして取得します。