Railsでセキュアなパスワードを実装する方法[備忘録]

<h1>セキュアなパスワードを実装する</h1>


<h2>ユーザ認証について</h2>
ユーザ認証は以下の手順で行われます。

<ol>
<li>パスワードの送信。</li>
<li><strong>ハッシュ化(ハッシュ関数を使って入力されたデータを不可逆なデータにする。)</li>
<li>データベース内のハッシュ化された値との比較</li>
</ol>

今回は特にパスワードのハッシュ化の実装について学んだことを残しておこうと思います。

<h3>has_secure_password</h3>
ハッシュ化されたパスワードを実装する時は、has_secure_passwordメソッドを使います。

このメソッドを使用すると以下のことができるようになります。

<h6>
<ol>
<li>ハッシュ化したパスワードを、データベース内のpassword_digestという属性に保存できるようになる。</li>
<li>authenticateメソッドが使えるようになる。(引数に正しいパスワードを入力すると、正しいユーザ情報を返し、間違ったパスワードだとfalseを返すメソッド)</li>
</ol>
</h6>


<h4>使用条件</h4>
<h5>・モデル内にpassword_digestという属性が含まれている。</h5>

というわけで、has_secure_passwordを使用するためにpassword_digestという属性をusersテーブルに追加していこうと思います。


<h2>password_digestカラム用の適切なマイグレーションを作る。</h2>
まずは、ハッシュ化したパスワードを保存するpassword_digestカラムのマイグレーションを生成します。

```Bash
rails generate migration add_password_digest_to_users password_digest:string
```
こちらのコードで以下のファイルとマイグレーションが生成されます。

```Ruby:db/migrate/20221228005859_add_password_digest_to_users.rb
class AddPasswordDigestToUsers < ActiveRecord::Migration[6.0]
  def change
    add_column :users, :password_digest, :string
  end
end
```
to_usersを末尾につけることで,add_column以下のコードが自動生成されています。
to_カラム名を末尾につけると指定したテーブルに引数で渡したカラムを追加するコードを自動生成してくれます。

ここでデータベースにマイグレーションを実行しましょう。

```
rails db:migrate
```

実際にカラムが追加されているか[DB Browser for SQlite](https://sqlitebrowser.org/)で確認してみましょう。

![スクリーンショット 2022-12-28 11.34.57.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2708737/b42f8eee-a762-e150-ac0e-1a1b7e0e450f.png)

無事追加されていることができました。

ここまでで、password_digestカラムを生成することがでいきました。
<h2>has_secure_passwordを使うために</h2>
password_digestカラムを作成する以外にもやることはあります。has_secure_passwordを使用するためにはハッシュ関数が必要です。なのでハッシュ関数であるbcryptをインストールします。

Gemfileにbcryptを追加します。

```Ruby:Gemfile
gem `bcrypt` `3.1.13`
```

```
bundle install
```
これでハッシュ関数が使えるようになりました。
次に、いよいよhas_secure_passwordを使用していきます。

<h2>has_secure_passwordを使ってみる</h2>
has_secure_passwordはUserモデルに記述します。

```Ruby:user.rb
class User < ApplicationRecord
    before_save {self.email = email.downcase}
    validates :name, presence: true, length: {maximum: 50}
    VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i
    validates :email, presence: true, length: {maximum: 255},
                            format: { with: VALID_EMAIL_REGEX },
                            uniqueness: { case_sensitive: true }
    has_secure_password #こちらを追加
end
```
has_secure_passwordの上のコードについては[こちら](https://qiita.com/Ktsuki16/items/5acfd9653fc0a894762c)を参照ください。

これでhas_secure_passwordは実装されました。しかし、このままだとバリデーションとして不完全です。

has_secure_passwordメソッドでは仮想的なpassword属性とpassword_confirmation属性に対してバリデーションする機能が強制的についています。そのためそれらについてのバリデーションを追加する必要があります。


<h2>パスワードの最小文字数を設定する</h2>
ここでは、password属性とpassword_confirmation属性のバリデーションを作成していきます。

今回はの2点についてバリデーションします。

<ul>
<li>パスワードが空でないこと。</li>
<li>最小文字数が6文字であること。</li>
</ul>

以下のコードをUserモデルに追加します。

```Ruby
validates :password, presence: true, length: { minimum:6 }
```
presence:は空でないことについて定義します。length:は長さです。minimum:オプションで最小の長さについて定義しています。


<h2>ユーザの作成と認証</h2>
では、最後にユーザの作成と認証を実際に行ってみます。

今回はrailsコンソールを使用します。

```Ruby:rails console
user = User.create(name: "Katsuki Sanada", email: "sana@icloud.com",
                 password: "111111", password_confirmation: "111111")
=> #<User id: 1, name: "Katsuki Sanada", email: "sana@icloud.com",
     created_at: "2022-12-28 01:36:13", updated_at: "2022-12-28 01:36:13", 
     password_digest: [FILTERED]>
```
これで、ユーザを作成することができました。

認証にはauthenticateメソットを使います。

```Ruby:rails console
user.authenticate("111111")
=> #<User id: 1, name: "Katsuki Sanada", email: "sana@icloud.com",
 created_at: "2022-12-28 01:36:13", updated_at: "2022-12-28 01:36:13",
 password_digest: [FILTERED]>
```
正しいパスワードを入力すると、しっかり登録したユーザを返してくれます。

間違ったパスワードを入れたらどうでしょうか？

```Ruby:rails console
user.authenticate("333333")
=>false
```
こちらはfalseを返してくれているので、認証は成功していると言えそうです。

<h1>まとめ</h1>
<strong>
<ul>
<li>安全なパスワード認証のためにはhas_secure_passwordを使う。</li>
<li>ポイントはハッシュ化。</li>
</ul>
</strong>

今回の学習はとても刺激的だなぁと思いました。次の内容が楽しみです。

<h1>参考文献</h1>
[Railsチュートリアル](https://railstutorial.jp/chapters/modeling_users?version=6.0#sec-adding_a_secure_password)