deviseは、Webアプリケーションを作成する際にほぼ必ずと言っていいほど必要になる認証系の機能を網羅的に用意してくれるgemです。アカウントの登録やログイン、パスワードの再設定機能、アカウントロック機能等、豊富な機能が利用できます。各機能はモジュールに分解されているため、自分のアプリケーションに必要な機能だけを選んで導入することが可能になっています。

また、複数モデルの同時ログインにも対応しています。完全に影響を分割することができるので、管理ユーザーと一般ユーザーが1つのアプリに存在する場合でもそれぞれに異なる認証方式を導入することができます。

言語、ライブラリのバージョン

  • Ruby 2.6.3
  • Rails 6.0.2.2
  • devise 4.7.1
  • テンプレートはslimを使用

始める前に

erb→slimにする

erbを利用する方はここは読み飛ばしてしまって構いません。slimを使う方はここで設定をしておきましょう。

deviseがviewファイルを自動生成する際にslimで生成してほしいので、Gemfileに

gem 'slim-rails'

を記載し、$ bundle install します。

これだけで今後viewファイル自動生成関係のコマンドではslimファイルが生成されます。

すでに存在するerbファイルをslimに変換する場合

生成済みのerbをslimに変換する場合、 html2slim を使うと手っ取り早いです。ファイル数が少なければ手動でも対応可能ですが、gemとコマンドを利用すればファイル名の変換から中身の置換までを正確に行えるため導入しておきましょう。

gemのインストール

$ gem install html2slim

ファイルの変換

$ for i in app/views/**/*.erb; do erb2slim $i ${i%erb}slim && rm $i; done

セットアップ

まずはセットアップです。使用するためにはdeviseのgemをインストールしておく必要があります。Gemfileに追記をしましょう。

Gemfile

source 'https://rubygems.org'
git_source(:github) { |repo| "https://github.com/#{repo}.git" }
.
.
中略
.
.
gem 'devise' #追記

下記コマンドを実行し、Gemfileの記載内容をもとにgemをインストールします。

$ bundle install

無事インストールできたらファイル生成のコマンドが利用できるようになっているはずなので、このコマンドを実行しましょう。

$ rails g devise:install

下記のような内容のログが表示されます。

      create  config/initializers/devise.rb
      create  config/locales/devise.en.yml
===============================================================================

Some setup you must do manually if you haven't yet:

  1. Ensure you have defined default url options in your environments files. Here
     is an example of default_url_options appropriate for a development environment
     in config/environments/development.rb:

       config.action_mailer.default_url_options = { host: 'localhost', port: 3000 }

     In production, :host should be set to the actual host of your application.

  2. Ensure you have defined root_url to *something* in your config/routes.rb.
     For example:

       root to: "home#index"

  3. Ensure you have flash messages in app/views/layouts/application.html.erb.
     For example:

       <p class="notice"><%= notice %></p>
       <p class="alert"><%= alert %></p>

  4. You can copy Devise views (for customization) to your app by running:

       rails g devise:views

===============================================================================

このコマンドで生成されるのは

  1. deviseの設定ファイル
  2. deviseの英語用のlocaleファイル

です。また、セットアップ方法も出力されるのでそれに従って進めていきます。

1.デフォルトのurlオプションを設定する

 1. Ensure you have defined default url options in your environments files. Here
     is an example of default_url_options appropriate for a development environment
     in config/environments/development.rb:

       config.action_mailer.default_url_options = { host: 'localhost', port: 3000 }

     In production, :host should be set to the actual host of your application.

ここに書かれているように、deviseで使用するdefault_url_optionを設定します。メールの存在確認などで生成されるリンクのurlの基本設定です。

開発環境ではconfig/environments/development.rb

config.action_mailer.default_url_options = { host: 'localhost', port: 3000 }

と設定します。 本番環境では、hostの部分にアプリケーションのドメインを入力しましょう。

config/environments/produvtion.rb

config.action_mailer.default_url_options = { :host => ドメイン, :protocol => 'https'}
  1. トップページへアクセスしたときのルーティングを設定する

これは既存のアプリケーションなどにdeviseを導入する場合など、トップページにアクセスした場合のルーティングが設定されている場合はスキップしても構わないです。今回は作っていないのでroutes.rbファイルにコントローラーとルーティングを設定します。

homeコントローラーを作成

rails g controller home index

routes.rbroot to: 'home#index'と記載 $ rails s を行って http://localhost:3000 にアクセスした場合にこのようになればOKです。(変換前なのでerbになってます。)

image-01

  1. flash メッセージを表示するために、 views/layouts/application.html.slim に追記します。
doctype html
html
  head
    title
      | DeviseCommentaly
    = csrf_meta_tags
    = csp_meta_tag
    = stylesheet_link_tag ‘application’, media: ‘all’, ‘data-turbolinks-track’: ‘reload’
    = javascript_pack_tag ‘application’, ‘data-turbolinks-track’: ‘reload’
  body
    p.notice = notice #追記
    p.alert = alert #追記
    = yield

最後に、deviseのviewファイルをカスタマイズするために

$ rails g devise:views

コマンドを実行します。すると下記のviewファイルが作成されます。

app/views/devise/confirmations/new.html.slim
app/views/devise/mailer/confirmation_instructions.html.slim
app/views/devise/mailer/email_changed.html.slim
app/views/devise/mailer/password_change.html.slim
app/views/devise/mailer/reset_password_instructions.html.slim
app/views/devise/mailer/unlock_instructions.html.slim
app/views/devise/passwords/edit.html.slim
app/views/devise/passwords/new.html.slim
app/views/devise/registrations/edit.html.slim
app/views/devise/registrations/new.html.slim
app/views/devise/sessions/new.html.slim
app/views/devise/shared/_error_messages.html.slim
app/views/devise/shared/_links.html.slim
app/views/devise/unlocks/new.html.slim

これらはパスワード再設定やメールアドレス変更のために利用します。

deviseをmodelに適用する

model新規作成の場合

deviseで制御するmodelを作成していきます。一般的に利用されるのはUserモデルだと思うので、今回はUserモデルを作成します。

$ rails g devise user

このコマンドでは以下の処理が実行されます

  • devise用のmigrationファイル生成
  • Userモデルのファイル生成
  • routes.rbに、devise用のルーティングを定義する devise_for :users を追記

migrationファイルの中身はこのようになっています。

# frozen_string_literal: true

class DeviseCreateUsers < ActiveRecord::Migration[6.0]
  def change
    create_table :users do |t|
      ## Database authenticatable
      t.string :email,              null: false, default: ""
      t.string :name,               null: false  # 追加
      t.string :encrypted_password, null: false, default: ""

      ## Recoverable
      t.string   :reset_password_token
      t.datetime :reset_password_sent_at

      ## Rememberable
      t.datetime :remember_created_at

      ## Trackable
      # t.integer  :sign_in_count, default: 0, null: false
      # t.datetime :current_sign_in_at
      # t.datetime :last_sign_in_at
      # t.inet     :current_sign_in_ip
      # t.inet     :last_sign_in_ip

      ## Confirmable
      # t.string   :confirmation_token
      # t.datetime :confirmed_at
      # t.datetime :confirmation_sent_at
      # t.string   :unconfirmed_email # Only if using reconfirmable

      ## Lockable
      # t.integer  :failed_attempts, default: 0, null: false # Only if lock strategy is :failed_attempts
      # t.string   :unlock_token # Only if unlock strategy is :email or :both
      # t.datetime :locked_at


      t.timestamps null: false
    end

    add_index :users, :email,                unique: true
    add_index :users, :reset_password_token, unique: true
    # add_index :users, :confirmation_token,   unique: true
    # add_index :users, :unlock_token,         unique: true
  end
end

コメントアウトされているいくつかの定義は冒頭で紹介したいくつかの機能を実現するために使用されるカラム群です。

今回は使用しないのでデフォルトのままでいきたいと思います。また、他のカラムをUserモデルに持たせたい場合はここに追記することもできます。今回はnameというカラムを追加して名前が登録できるようにしました。

なお、生成されたUserモデルはこのような内容です。

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

バージョンによって多少の差はありますが、今回の構成だと

database_authenticatable   #=> ログイン時に正当性を検証する機能。認証はPOSTリクエストかHTTPベーシック認証が使える
registerable    #=> 新規登録処理を実行しDBに保存する機能、またそのアカウントを編集、削除できるようにする機能
recoverable    #=> パスワードのリセットを行い、変更手順を送る機能
rememberable   #=>   保存されたCookieからユーザー情報を記憶するためのトークンを管理する機能
validatable    #=> デフォルトでe-mailとパスワードの検証を提供する。optionであるためカスタマイズができる。

という機能がmodel生成時点で有効になっています。

続いてmigrationを行い、DBに定義を反映します。

$ rails db:migrate

これでdeviseコマンドで作成されたmigrationが反映されました。

ログイン画面がうまく表示されるか確認してみましょう。 $ rails s でサーバーを起動し、/users/sign_up にアクセスしてみます。

image-02

このように表示されていることが確認できれば成功です。

modelがすでに存在する場合

あまりないケースかもしれませんが、既にUserモデルが存在しており、そのモデルに対してdeviseを使用したい場合でも、

$ rails g devise user

このコマンドを実行することで適切なmigrationファイルが生成され、deviseの機能が使えるようになります。

ただし、既に同名のカラムが存在している場合はmigration実行時にエラーになりますので、重複している部分を削除してから実行を行ってください。

deviseでコントローラーをカスタマイズする(新規登録処理)

先ほどUserモデルにnameカラムを追加したのでdevise:viewsで作成したファイルに、nameを入力できるようにフォームを改修します。

/app/views/devise/registrations/new.html.slim

h2
  | Sign up
= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f|
  = render "devise/shared/error_messages", resource: resource
  .field
    = f.label :name
    br
    = f.text_field :name
  .field
    = f.label :email
    br
    = f.email_field :email, autofocus: true, autocomplete: "email"
  .field
    = f.label :password
    - if @minimum_password_length
      em
        | (
        = @minimum_password_length
        |  characters minimum)
    br
    = f.password_field :password, autocomplete: "new-password"
  .field
    = f.label :password_confirmation
    br
    = f.password_field :password_confirmation, autocomplete: "new-password"
  .actions
    = f.submit "Sign up"
= render "devise/shared/links"

ただ、このままだとsign_upの処理はdeviseのデフォルトのコントローラーが処理を行うため、独自に追加したnameというカラムはそのままでは保存できません。 このような場合はdeviseのコントローラーを上書きして、必要な処理を追加してあげる必要があります。今回はUserモデルに対してなので

$ rails g devise:controllers users

とターミナルに入力します。 出力されるログは以下。

 create  app/controllers/users/confirmations_controller.rb
      create  app/controllers/users/passwords_controller.rb
      create  app/controllers/users/registrations_controller.rb
      create  app/controllers/users/sessions_controller.rb
      create  app/controllers/users/unlocks_controller.rb
      create  app/controllers/users/omniauth_callbacks_controller.rb
===============================================================================

Some setup you must do manually if you haven't yet:

  Ensure you have overridden routes for generated controllers in your routes.rb.
  For example:

    Rails.application.routes.draw do
      devise_for :users, controllers: {
        sessions: 'users/sessions'
      }
    end

===============================================================================

このコマンドで新規登録やログインなどそれぞれの処理の際に呼ばれるコントローラーが生成されます。また、オーバーライドしたコントローラーにルーティングを渡したいので、例にあるようにroutes.rb

devise_for :users, controllers: {
       registrations: 'users/registrations'
       sessions: 'users/sessions'
}

とルーティングを設定します。この記述によってusersにおける処理の行き先を指定しています。

ルーティングを指定し、コントローラーが自分の作成したものを経由するようになったらnameカラムを保存できるようにcontrollerに手を加えていきます。ちなみに自動生成されたusers/registrations_controller.rbは下記のようになっています。

# frozen_string_literal: true

class Users::RegistrationsController < Devise::RegistrationsController
  # before_action :configure_sign_up_params, only: [:create]
  # before_action :configure_account_update_params, only: [:update]

  # GET /resource/sign_up
  # def new
  #   super
  # end

  # POST /resource
  # def create
  #   super
  # end

  # GET /resource/edit
  # def edit
  #   super
  # end

  # PUT /resource
  # def update
  #   super
  # end

  # DELETE /resource
  # def destroy
  #   super
  # end

  # GET /resource/cancel
  # Forces the session data which is usually expired after sign
  # in to be expired now. This is useful if the user wants to
  # cancel oauth signing in/up in the middle of the process,
  # removing all OAuth session data.
  # def cancel
  #   super
  # end

  # protected

  # If you have extra params to permit, append them to the sanitizer.
  # def configure_sign_up_params
  #   devise_parameter_sanitizer.permit(:sign_up, keys: [:attribute])
  # end

  # If you have extra params to permit, append them to the sanitizer.
  # def configure_account_update_params
  #   devise_parameter_sanitizer.permit(:account_update, keys: [:attribute])
  # end

  # The path used after sign up.
  # def after_sign_up_path_for(resource)
  #   super(resource)
  # end

  # The path used after sign up for inactive accounts.
  # def after_inactive_sign_up_path_for(resource)
  #   super(resource)
  # end
end


このファイルの中の before_action :configure_sign_up_params, only: [:create]

def configure_sign_up_params
  devise_parameter_sanitizer.permit(:account_update, keys: [:attribute])
end

のコメントアウトを外しておきます。 nameカラムに値を入れたいので、:attributeとなっている部分を下記のように:nameに変えます。複数ある場合はカンマ区切りで設定することができます。

devise_parameter_sanitizer.permit(:account_update, keys: [:name])

この部分を変更することによって任意の値を新規登録時に保存させることができます。 この段階ではconfirmableを有効にしていないので、新規登録が正常に実行されるとdeviseがログイン処理も行います。 ログイン後はデフォルトだとroot_pathにリダイレクトされるので、今回はHome#indexに遷移します。

deviseでコントローラーをカスタマイズする(ログアウト)

一連の動作を何度でも行えるように、ログアウト処理をつけておきましょう。 ログイン処理後に遷移するhome#indexにログアウト用のリンクをつけましょう。


h1
  | Home#index
p
  | Find me in app/views/home/index.html.slim

= link_to 'ログアウト', destroy_user_session_path, method: :delete

これでボタンを押せばログアウトできるようになりました。

ログインに伴う制御を行う

現状までの状態で新規登録を行いそのままログイン、ログアウトまでの流れを実装できましたが、このような認証機能を入れたい理由の大半はログインしなければアクセスできないページが必要だからということが多いです。 今回はログインしていればアクセスすることができ、新規登録の際に登録したnameを表示するだけの簡単なmypageを作ってそれを検証してみます。

$ rails g controller mypage show

my page用のコントローラーを作成します。今回使用するのはshowメソッドにするので、showもこのタイミングで設定してしまいます。 routes.rbには自動でget 'mypage/show'のようなメソッドが生成されているのでこのままアクセスできます。今回はそのままこのルーティングを使ってアクセスしていきます。

image-03

ではこのページに制御をかけていきます。 deviseを導入すると使えるようになるメソッドがいくつか提供されていて、その中で認証を行うためには authenticate_user!というメソッドを使用します。今回はbefore_actionを使用してmypageコントローラー全体に認証を設定します。

/app/controllers/mypages_controller.rb

class MypageController < ApplicationController
  before_action :authenticate_user!  #追加

  def show
  end
end

これだけです。これでdeviseがログイン済みなのかどうか判定してくれます。この状態で先ほどの mypage/showにアクセスしてみましょう。

ログアウトした状態でmypage/showにアクセスするとログインページにリダイレクトされ、画像のようにデフォルトメッセージとして You need to sign in or sign up before continuing.と表示されます。

image-04

これでログインしているときにしか表示させないページという制御ができるようになったので、このページにログインしたユーザーの名前を表示していきます。

現在ログインしているユーザーを取得するにはdeviseをインストールすることで使えるようになったメソッドを使います。Userモデルに適用しているのでcurrent_userというメソッドが利用できます。userという部分は適用するモデルによって変化するので、もしPersonというモデルに適用している場合であればcurrent_personになります。

mypage/show.html.slimに下記のように記載します

/app/views/mypages/show.html.slim

h1 Mypage#show
p Find me in app/views/mypage/show.html.slim

//追加
= current_user.name

この状態でログインしてmypage/showにアクセスしてみましょう。 今回の例ではnameには「test」という名前で登録をしているので、testとmypage/showに表示されれば成功です。

image-05

まとめ

deviseの導入から基本的な使い方を解説しました。deviseは色々なカスタマイズに対応できる作りとなっており、要件にあわせて適切に改修していくことが可能です。カスタマイズについては他の記事で解説をしていますので、興味があればdeviseタグから探してみてください。