Rails7.1環境で認証機能Rails deviseの導入方法

この記事はウェブクルー Advent Calendar 2023 5日目の記事です。
昨日は@wc-horino さんの「Scala, PlayFramework, Mockitoを使った自動テスト導入」でした。

# Rails-deviseとは
Deviseは、Ruby on Railsフレームワークの人気のある認証ライブラリです。ユーザーのサインアップ、サインイン、パスワードのリセットなど、強力なセキュリティ機能および認証機能を簡単に実装できるようなGemライブラリーです。


# 開発環境情報

```
# rubyバージョン
$ rbenv versions
* 3.2.2

# railsバージョン、Turbo Frame有効済み
$ rails -v
Rails 7.1.2
```

# 参考資料
公式ドキュメント

https://rubydoc.info/github/heartcombo/devise/

Github

https://github.com/heartcombo/devise

# Rails-deviseのインストールと初期設定

**Rails-deviseのインストール**
```ruby:Gemfile
gem "devise"
```

```terminal:terminal
bundle install
rails g devise:install
```
deviceをインストールしたら、deviseのconfigファイル（/config/initializers/devise.rb）が作成されました。
今回は特に編集しなくて大丈夫です。

## deviseのUserモデルの作成

```terminal:terminal
rails g devise User
```

userモデルとroutesの記述が自動に追加されました。

```ruby:/app/models/user.rb
class User < ApplicationRecord
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable
end
```

`devise_for :users`この一行の追加により、以下のようなルーティングが自動的に設定されます:
サインアップ (/users/sign_up)
ログイン (/users/sign_in)
サインアウト (/users/sign_out)
パスワードリセット (/users/password/new, /users/password/edit など)
```ruby:/config/routes.rb
Rails.application.routes.draw do
  devise_for :users
  get "home/index"
  root "home#index"

  get "up" => "rails/health#show", as: :rails_health_check

  # Defines the root path route ("/")
  # root "posts#index"
end
```

## マイグレーションファイルでカスタマイズカラムを追加
```terminal:terminal
rails g migration AddColumnToUser
```
/db/migrate/*****_add_column_to_user.rbファイルが生成されました。
このマイグレーションファイルでfull_name、username、phone_numberのカラムをusersテーブルに追加します。
```ruby:/db/migrate/*****_add_column_to_user.rb
class AddColumnToUser < ActiveRecord::Migration[7.1]
  def change
    add_column :users, :full_name, :string
    add_column :users, :username, :string
    add_column :users, :phone_number, :string
  end
end
```

マイグレーションファイルを実行します。
```
rails db:migrate
```
schemaファイルを確認してみると、
full_name、username、phone_numberが無事にusersテーブルに追加されました。
```ruby:/db/schema.rb
ctiveRecord::Schema[7.1].define(version: 2023_12_04_131204) do
  create_table "users", force: :cascade do |t|
    t.string "email", default: "", null: false
    t.string "encrypted_password", default: "", null: false
    t.string "reset_password_token"
    t.datetime "reset_password_sent_at"
    t.datetime "remember_created_at"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
   +t.string "full_name"
   +t.string "username"
   +t.string "phone_number"
    t.index ["email"], name: "index_users_on_email", unique: true
    t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
  end
end
```
## コントローラーにdeviseのメソッドなどを登録
参考：　https://github.com/heartcombo/devise#strong-parameters
```ruby:/app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  before_action :configure_permitted_parameters, if: :devise_controller?

  protected

  def configure_permitted_parameters
    # /users/sign_up
    devise_parameter_sanitizer.permit(:sign_up, keys: [:username, :phone_number, :full_name])
  end
end
```

**ruby7.0以降はTurbo frame機能がデフォルトになっているため、Turboと組み込む必要があります。**
```ruby:/app/controllers/turbo_devise_controller.rb
class TurboDeviseController < ApplicationController
  class Responder < ActionController::Responder
    def to_turbo_stream
      controller.render(options.merge(formats: :html))
    rescue ActionView::MissingTemplate => error
      if get?
        raise error
      elsif has_errors? && default_action
        render rendering_options.merge(formats: :html, status: :unprocessable_entity)
      else
        redirect_to navigation_location
      end
    end
  end

  self.responder = Responder
  respond_to :html, :turbo_stream
end
```

## deviseのviewsの作成
deviseのデフォルトのビューをカスタマイズするためのファイルがアプリケーション内に生成されます。
/app/views/deviseのフォルダーが生成されて、
今回は`/sessions/new.html.erb`と`/registrations/new.html.erb`だけを編集します。

```ruby:terminal
rails generate devise:views
```


ログインページのマークアップ
![スクリーンショット 2023-12-05 0.41.10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627719/d78a3abe-b9e3-5995-4b82-9060f1a8549d.png)

```ruby:/app/views/devise/sessions/new.html.erb
<div class="container home-sections d-flex flex-row justify-content-center
  align-items-center"
>
  <div class="images-section pt-3 d-none d-lg-block"></div>

  <div class="login-section pt-3">
    <div class="card text-center pt-5" style="width: 23rem;">
      <h1>ログイン画面</h1>
      <div class="card-body">
        <%= form_for(resource, as: resource_name, url: session_path(resource_name)) do |f| %>
          <div class="mb-3 mt-4">
            <%= f.email_field :email, class: "form-control", placeholder: "電話番号, ユーザ名, Eメール" %>
          </div>

          <div class="mb-3">
            <%= f.password_field :password, class: "form-control", placeholder: "パスワード" %>
          </div>

          <%= f.submit "ログイン", class: "btn btn-primary w-100" %>
        <% end %>

        <% if notice %>
          <p class="notice"><%= notice %></p>
        <% elsif alert %>
          <p class="alert"><%= alert %></p>
        <% end %>

        <a href="#" class="fs-6 d-block my-4 text-decoration-none"> パスワードを忘れた？</a>
      </div>
    </div>

    <div class="card text-center py-2 mt-3" style="width: 23rem;">
      <div class="card-text">
        アカウントを持っていない？
        <%= link_to "サインアップ", new_user_registration_path, class: "fs-6 my-4 text-decoration-none text-info" %>
      </div>
    </div>
  </div>
</div>
```


登録ページのマークアップ
![スクリーンショット 2023-12-05 0.41.21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627719/caf521b9-8d93-8ee7-7fc5-dd8835555e3c.png)

```ruby:/app/views/devise/registrations/new.html.erb
<div class="container home-sections d-flex flex-row justify-content-center
  align-items-center"
>
  <div class="login-section pt-3">
    <div class="card text-center pt-5 mx-auto" style="width: 23rem;">
      <h1>サインアップ画面</h1>

      <div class="card-body">
        <%= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %>
          <div class="mb-3 mt-4">
            <%= f.email_field :email, class: "form-control", placeholder: "電話番号、Eメール" %>
          </div>

          <div class="mb-3 mt-4">
            <%= f.text_field :full_name, class: "form-control", placeholder: "お名前" %>
          </div>

          <div class="mb-3 mt-4">
            <%= f.text_field :username, class: "form-control", placeholder: "ユーザ名" %>
          </div>

          <div class="mb-3">
            <%= f.password_field :password, class: "form-control", placeholder: "パスワード" %>
          </div>

          <div class="mb-3">
            <%= f.password_field :password_confirmation, class: "form-control", placeholder: "パスワード確認" %>
          </div>

          <%= f.submit "サインアップ", class: "btn btn-primary w-100" %>
        <% end %>

        <% if notice %>
          <p class="notice"><%= notice %></p>
        <% elsif alert %>
          <p class="alert"><%= alert %></p>
        <% end %>
      </div>
    </div>

    <div class="card text-center py-2 mt-3 mx-auto" style="width: 23rem;">
      <div class="card-text">
        既にアカウントを持っている？
        <%= link_to "サインイン", new_user_session_path, class: "fs-6 my-4 text-decoration-none text-info" %>
      </div>
    </div>
  </div>
</div>
```

**ログインしていない時**
![スクリーンショット 2023-12-05 0.41.36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627719/3d7f0c0a-1952-c124-f884-be9574e05080.png)

**ログインした後**
![スクリーンショット 2023-12-05 0.41.57.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627719/5d2ffc1f-4339-4d6b-cc24-c21ea7d1fd73.png)

## 開発環境で確認
**サンプルをご参考ください。**

https://github.com/wc-pei/rails-devise-practice

* ユーザ登録ページ http://localhost:3000/users/sign_up
* ログインページ http://localhost:3000/users/sign_in
* トップページ http://localhost:3000

# 終わりに
deviseはRuby on Railsアプリケーションにおいて、ユーザー認証とセッション管理の実装を驚くほど簡単で同時に豊富な機能を提供しています。開発者はセキュリティに焦点を当てることなくてアプリケーションのコア機能に専念できます。ぜひ試して見てください。

明日は@wchikarusato さんの「obsidianを使って1週間を振り返る方法」になります。
お楽しみにしてください。