🦓

[Rails]deviseによるユーザー認証 1/2

2023/07/15に公開

はじめに

devisegemを使ってrailsアプリに認証機能を導入していきます。
ドキュメントを参考しながらやっていきます。
https://github.com/heartcombo/devise

環境:

Rails 6.1.7.3
ruby 3.0.0

deviseをインストールする

Gemfile
gem 'devise'
bundle install

deviseを生成する

bin/rails generate devise:install
      create  config/initializers/devise.rb
      create  config/locales/devise.en.yml

devise.en.ymlはフラッシュメッセージのファイルとなります。
同じディレクトリ内にdevise.ja.ymlを作成し、日本語のフラッシュメッセージを追加します。
https://github.com/tigrish/devise-i18n/blob/master/rails/locales/ja.yml

メーラー用URLを追加する

開発環境でメールを確認したい場合はletter_opener_webというgemと一緒に使うと便利です。

config/environments/development.rb
Rails.application.configure.do
  # letter_opener
  config.action_mailer.delivery_method = :letter_opener_web
  config.action_mailer.perform_deliveries = true
  
  config.action_mailer.default_url_options = { host: 'localhost', port: 3000 }
end

メーラー用のurlを追加します。

config/routes.rb
Rails.application.routes.draw do
  mount LetterOpenerWeb::Engine, at: '/letter_opener' if Rails.env.development?
end

Userモデルを作成する

bin/rails generate devise user
      invoke  active_record
      create    db/migrate/20230714115356_add_devise_to_users.rb
      insert    app/models/user.rb
       route  devise_for :users

コメントアウトされたモジュールを有効化にする場合マイグレーションファイルにコメントアウトされたカラムを解除してからマイグレーションしましょう。

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

各モジュールとそれぞれの機能について:

  1. :database_authenticatable: パスワードベースの認証を提供します。パスワードは暗号化され、データベースに保存されます。

  2. :registerable: ユーザーの新規登録と編集を可能にします。

  3. :recoverable: パスワードリセット機能を提供します。ユーザーはパスワードリセット用のメールを受け取り、リンクをクリックすることで新しいパスワードを設定できます。

  4. :rememberable: ユーザーのログイン状態を記憶し、ブラウザのセッションを保持します。

  5. :trackable: ユーザーのログイン回数、最終ログイン時刻などのトラッキング情報を保存します。

  6. :validatable: ユーザーモデルのバリデーションを提供します。メールアドレスの一意性やパスワードの複雑さなどのバリデーションを簡単に設定できます。

  7. :confirmable: メール確認機能を提供します。ユーザーは登録メールの確認リンクをクリックしてアカウントを有効化する必要があります。

  8. :lockable: ユーザーアカウントのロックアウト機能を提供します。一定回数のログイン試行失敗後にアカウントがロックされます。

  9. :timeoutable: ユーザーセッションのタイムアウト機能を提供します。一定時間アクティビティがない場合にセッションを自動的にログアウトします。

  10. :omniauthable: OmniAuthを使用したソーシャルログイン(Facebook、Twitter、Googleなど)を簡単に実装するための機能を提供します。

bin/rails db:migrate
Running via Spring preloader in process 14619
== 20230714115356 AddDeviseToUsers: migrating =================================
-- change_table(:users)
   -> 0.0052s
-- add_index(:users, :email, {:unique=>true})
   -> 0.0118s
-- add_index(:users, :reset_password_token, {:unique=>true})
   -> 0.0008s
== 20230714115356 AddDeviseToUsers: migrated (0.0180s) ========================

has_secure_passwordを削除する

Railsが用意してくれたhas_secure_passwordを使ってpassword_digestでユーザーのパスワードを管理してましたが、deviseでは代わりにencrypted_passwordがあるためhas_secure_passwordを削除します。

ヘルパーメソッド

Deviseには、ユーザー認証に関連するさまざまなヘルパーメソッドが用意されています。

メソッド 使い方
authenticate_user! ユーザーが認証されていない場合、このヘルパーメソッドを使用してリクエストを中断し、ログインページにリダイレクトします。アクションの前にフィルターとして使用されることがあります。
current_user 現在ログインしているユーザーのインスタンスを返します。アプリケーションのどこからでもログインしているユーザーの情報にアクセスするために使用できます。
user_signed_in? ユーザーがログインしているかどうかを判定します。ログインしている場合はtrue、ログアウトしている場合はfalseを返します。
user_session ユーザーのセッション情報を管理するためのヘルパーメソッドです。セッションを明示的に操作する必要がある場合に使用します。

authenticate_user!を使う

Deviseのuser_signed_in?メソッドを使用してユーザーがログインしているかどうかをチェックします。
ユーザーがログインしていない場合、現在のページを保存し、ユーザーをログインページにリダイレクトします。ユーザーがログインした後に元のリクエストされたURLにリダイレクトされます。

devise/lib/devise/controllers/helpers.rb
module Devise
  module Controllers
    module Helpers
      def authenticate_user!
        unless user_signed_in?
          store_location_for(:user, request.url)
          redirect_to new_user_session_path
        end
      end
      # ...
    end
  end
end

Articlesコントローラーにbefore_actionを追加し、特定のアクションやページに対して認証を要求することができます

app/controllers/articles_controller.rb
class ArticlesController < ApplicationController
  before_action :authenticate_user!, only: %i[new create edit update destroy]
end

ルーティングを追加する

devise_forは認証関連のルーティングを作成してくれるメソッドです。

config/routes.rb
Rails.application.routes.draw do
  devise_for :users
end

作成されるルーティングはこちらになります。
https://www.rubydoc.info/github/heartcombo/devise/main/ActionDispatch/Routing/Mapper%3Adevise_for

ログインする時のURLは/users/sign_inとなっていますが、カスタマイズすることができます。

config/routes.rb
Rails.application.routes.draw do
  devise_for :users, path: 'auth', path_names: { sign_in: 'login', sign_out: 'logout' }
end

新規登録フォーム

http://localhost:3000/users/sign_up

ログインフォーム

http://localhost:3000/users/sign_in

デフォルトで用意されたビューとなります。
新規登録画面とログイン画面を変更したい場合、カスタムビューを作成することができます。

カスタムビューを作成する

gemにあるビューファイルをapp/views/users/deviseディレクトリ配下に作成されます。

bin/rails generate devise:views
      invoke  Devise::Generators::SharedViewsGenerator
      create    app/views/devise/shared
      create    app/views/devise/shared/_error_messages.html.erb
      create    app/views/devise/shared/_links.html.erb
      invoke  form_for
      create    app/views/devise/confirmations
      create    app/views/devise/confirmations/new.html.erb
      create    app/views/devise/passwords
      create    app/views/devise/passwords/edit.html.erb
      create    app/views/devise/passwords/new.html.erb
      create    app/views/devise/registrations
      create    app/views/devise/registrations/edit.html.erb
      create    app/views/devise/registrations/new.html.erb
      create    app/views/devise/sessions
      create    app/views/devise/sessions/new.html.erb
      create    app/views/devise/unlocks
      create    app/views/devise/unlocks/new.html.erb
      invoke  erb
      create    app/views/devise/mailer
      create    app/views/devise/mailer/confirmation_instructions.html.erb
      create    app/views/devise/mailer/email_changed.html.erb
      create    app/views/devise/mailer/password_change.html.erb
      create    app/views/devise/mailer/reset_password_instructions.html.erb
      create    app/views/devise/mailer/unlock_instructions.html.erb

特定のビューだけをカスタマイズする場合オプションを入れることができます。

rails generate devise:views -v registrations confirmations

新規登録フォームをカスタマイズする

新規登録フォームにユーザー名を追加していきます。
deviseのストロングパラメーターにuser_nameを追加します。
追加する場所はApplicationコントローラーになります。

app/controlllers/application_controller.rb
class ApplicationController < ActionController::Base
  before_action :configure_permitted_parameters, if: :devise_controller?

  protected

  def configure_permitted_parameters
    devise_parameter_sanitizer.permit(:sign_up, keys: [:user_name])
  end
end

新規登録ビューにbootstrapスタイルを適用する

app/views/devise/registration/new.html.erb
<h2><%= t('devise.registrations.new.sign_up') %></h2>
<%= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %>
  <%= render "devise/shared/error_messages", resource: resource %>
  <div class="form-group">
    <%= f.label :user_name %><em>(<%= t('devise.shared.minimum_password_length', count: 3 ) %>)</em>
    <%= f.text_field :user_name, autofocus: true, class: 'form-control mb-3'%>
  </div>
  <div class="form-group">
    <%= f.label :email %><br />
    <%= f.email_field :email, autofocus: true, autocomplete: "email", placeholder: "user@example.com", class: 'form-control mb-3'%>
  </div>
  <div class="form-group">
    <%= f.label :password %>
    <% if @minimum_password_length %>
      <em>(<%= t('devise.shared.minimum_password_length', count: 6 ) %>)</em>
    <% end %><br />
    <%= f.password_field :password, autocomplete: "new-password", class: 'form-control mb-3'%>
  </div>
  <div class="form-group">
    <%= f.label :password_confirmation %><br />
    <%= f.password_field :password_confirmation, autocomplete: "new-password", class: 'form-control mb-3'%>
  </div>
  <%= f.submit class:'btn btn-primary' %>
<% end %>
<%= render "devise/shared/links" %>

ログインビューにbootstrapスタイルを適用する

app/views/divise/sessions/new.html.erb
<h2><%= t('devise.sessions.new.sign_in') %></h2>
<%= form_for(resource, as: resource_name, url: session_path(resource_name)) do |f| %>
  <div class="form-group">
    <%= f.label :email %><br />
    <%= f.email_field :email, autofocus: true, autocomplete: "email", class: 'form-control mb-3'%>
  </div>
  <div class="form-group">
    <%= f.label :password %><br />
    <%= f.password_field :password, autocomplete: "current-password", class: 'form-control mb-3'%>
  </div>
  <% if devise_mapping.rememberable? %>
    <div class="form-check mb-3">
      <%= f.check_box :remember_me, class:'form-check-input' %>
      <%= f.label :remember_me, class:'form-check-label' %>
    </div>
  <% end %>
  <%= f.submit class: 'btn btn-primary' %>
<% end %>
<%= render "devise/shared/links" %>

ヘッダービューの切り替え

ヘルパーメソッドuser_signed_in?を使ってログイン状態によってヘッダーの表示を切り替えます。
ルーティングも追加します。

app/views/shared/_header.html.erb
<ul class="navbar-nav ms-auto">
  <% if user_signed_in? %>
      <li class="nav-item">
          <% if current_user.profile.present? %>
             <%= image_tag current_user.profile.thumb.url %>
          <% else %>
             <%= image_tag 'user.png', id: 'preview-target', size:'50x50', class: 'round-circle' %>
          <% end %>
          <%= link_to current_user.user_name, class: "nav-link" %>
	  <li class="nav-item">
             <%= link_to t('devise.sessions.destroy.sign_out'), destroy_user_session_path, method: :delete, class: 'nav-link' %>
           </li>
       </li>
   <% else %>
       <li class="nav-item">
             <%= link_to t('devise.sessions.new.sign_in'), new_user_session_path, class: 'nav-link' %>
       </li>
       <li class="nav-item">
             <%= link_to t('devise.registrations.new.sign_up'), new_user_registration_path, class: 'nav-link' %>
       </li>
   <% end %>
</ul>

ターミナルからパスを確認することもできます。

rails routes | grep sign_out
destroy_user_session DELETE /users/sign_out(.:format)   devise/sessions#destroy

メーラーを確認する

新規アカウントの登録後に確認メールが届きました。

パスワードリセット機能用のメールです。

セッションのタイムアウト

モジュールの:timeoutableを有効化にしましたので、デフォルトで30分後アプリにログアウトされます。
タイムアウトまでの時間を伸ばすにはこの時間を変えましょう。

config/initializers/devise.rb
Devise.setup.do |config|
  config.timeout_in = 30.minutes
end

終わり

簡単ですがdeviseを使ったユーザー認証でした。

https://autovice.jp/articles/169

編集機能についての記事もあるのであわせて見て頂けますと幸いです。
https://zenn.dev/redheadchloe/articles/0cef8b1c4264cf

Discussion