Help us understand the problem. What is going on with this article?

Rails deviseによるユーザー認証 メールによる認証、emailとusernameのどちらでもログイン可能にするまで

概要

本記事では、Railsのgem「devise」を用いてユーザーログイン機能を実装していきます。
ステップにそって作業を進めれば、実装出来ます。

本記事でやること

  • 基本的なサインイン・ログイン・ログアウト
  • 確認メール付き認証
  • emailとusernameのどちらでもログイン可能にする

実装

deviseの導入

下記をGemfileに記述してbundle install。

Gemfile
gem 'devise'

その後、以下のコマンドでdeviseの設定ファイルの生成。

$ bundle exec rails g devise:install

以下のようにメッセージが表示されるのではじめのステップではこちらを参考に作業を進めます。

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

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. If you are deploying on Heroku with Rails 3.2 only, you may want to set:

       config.assets.initialize_on_precompile = false

     On config/application.rb forcing your application to not access the DB
     or load models when precompiling your assets.

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

       rails g devise:views

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

各種設定

default_url_optionsの設定

下記を追記。

config/environments/development.rb
config.action_mailer.default_url_options = { host: 'localhost', port: 3000 }

rootパスの設定

下記のようにrootパスを設定。ここは各々rootパスに設定したいものを設定。

config/routes.rb
root "home#index"

flashメッセージの設定

下記を追記。ひとまず、<body>の直下でいいかと。

app/views/layouts/application.html.erb
  <p class="notice"><%= notice %></p>
  <p class="alert"><%= alert %></p>

deviseのviewの生成

下記のコマンドでdeviseのviewの出力。

$ bundle exec rails g devise:views

Model、migrationファイルの作成

以下のコマンドでuserモデル及びマイグレーションファイルが作成されます。

$ bundle exec rails g devise user

余談

ちなみに、今回はUserモデルで作業を進めますが、

$ bundle exec rails g devise member

のようにモデル名は変更出来ます。このときはdeviseのメソッド名もモデル名に合わせて変わる点には注意が必要です。
例えば、user_signed_in?メソッドはmember_signed_in?となります。

認証に必要なカラムを追加する

デフォルトでは以下のファイルの22行目あたりがコメントアウトされているので以下の項目のコメントアウトをすべてはずす。メール認証のためのカラムで今回は使用します。

db/migrate/xxxxxxxxxxxx_devise_create_users.rb
  ## Confirmable
  t.string   :confirmation_token
  t.datetime :confirmed_at
  t.datetime :confirmation_sent_at
  t.string   :unconfirmed_email # Only if using reconfirmable

そして、migrate。

$ bundle exec rake db:migrate

deviseの各種設定

ログアウトボタンを設置する

ログアウトボタンをとりあえず作っておきましょう。(ログインのテストをするときにないと困るので)
先ほどのフラッシュメッセージの直下に下記を記述しておきましょう。
deviseがログアウトの処理は準備してくれているので、それを使用するだけです。
パスについてはrake routesで確認出来ます。
また、user_signed_in?というユーザーがログインしているかどうかを判別するメソッドを使用することで、ログイン時のみログアウトボタンを出力します。

app/views/layouts/application.html.erb
  <% if user_signed_in? %>
    <p><%= link_to "ログアウト", destroy_user_session_path, method: :delete %></p>
  <% end %>

ログインしていないときにログイン画面にリダイレクトをかける

下記を追加しましょう。:authenticate_user!によってログインしていないときログイン画面にリダイレクトをかけることが出来るようになります。ちなみに、ここではapplication_controller.rb内で使用しましたが、各コントロラー単位でも使用出来るので必要があれば書き換えて下さい。

app/controllers/application_controller.rb
before_action :authenticate_user!

メールに必要な設定を行う

メール認証のモジュールを読み込む

user.rbの4行目付近に:confirmableを読み込む。

app/model/user.rb
devise :database_authenticatable, :registerable,
       :recoverable, :rememberable, :trackable, :validatable, :confirmable

追記 2019.01.23

devise 4.5.0から :trackable はデフォルトではなくなったようです。:trackableを追加する必要はありません。

SMTPサーバーの設定

メールを実際に送信するためには、SMTPサーバーを設定しなければなりません。
ここでは、gmailを用いてメールの送信を行います。
以降でgmailを使用するのでメールアドレスとパスワードが必要になります

2段階認証をかけている場合は解除しなければ、使用できません。

config/environments/development.rb
config.action_mailer.smtp_settings = {
  :enable_starttls_auto => true,
  :address => "smtp.gmail.com",
  :port => 587,
  :domain => 'smtp.gmail.com',
  :user_name => "xxxxxxx@gmail.com", #gmailアドレス
  :password => "xxxxxxxxxxx", #gmailパスワード
  :authentication => 'login',
}

github等にアップロードしてソースコードを公開される場合は注意が必要です。自身のgmail
のアドレスとパスワードを公開することになるので勝手に使用されるおそれがあります。
その場合は、gemの「config」を使うことをおすすめします。
下記の記事の中で使用法について記述してあるので参考までに。

rails 一度gitHubにあげたID・パスワード等を履歴から消し、環境ごとに管理する

ここまでの確認をする

ここまで出来れば、メール認証つきのログイン認証の出来上がりです。
実際にサインイン・ログイン・ログアウトの一連の処理を試してみてください。
まだ、ここまででは通常のemailでの認証です。

SMTPにエラーが発生するときは「安全性の低いアプリのアクセス」をonにしていない可能性があります。
メールが届くと思うのでそれに沿って設定して下さい。
一応、下記のURLから設定出来るのでこちらからでも設定変更可能です。
https://www.google.com/settings/security/lesssecureapps

それでは、次からemailとusernameのどちらでもログイン可能にしていきます。

userテーブルにusernameカラムを追加する

migrationファイルの作成。

$ bundle exec rails g migration AddUserNameToUsers username:string:uniq

その後、

$ bundle exec rake db:migrate

ストロングパラメーターの設定

deviseのコントローラーはgemの中にあるため、直接変更出来ません。そのため、新たにカラムを追加したときは別途ストロングパラメーターの設定をする必要があります。
今回は:usernameをカラムとして追加し、さらにこの後に:loginをフォームの中で使用していくのでこちらの設定が必要となります。

app/controllers/application_controller.rb
# 以下を追加
  before_filter :configure_permitted_parameters, if: :devise_controller?

  protected
    def configure_permitted_parameters
      devise_parameter_sanitizer.for(:sign_up) { |u| u.permit(:username, :email, :password, :password_confirmation, :remember_me) }
      devise_parameter_sanitizer.for(:sign_in) { |u| u.permit(:login, :username, :email, :password, :remember_me) }
      devise_parameter_sanitizer.for(:account_update) { |u| u.permit(:username, :email, :password, :password_confirmation, :current_password) }
    end

追記 2017.2.27

Rails5の変更に伴いdeviseのバージョンがあがりストロングパラメータの追加の仕方が変わっている可能性があります。devise4をお使いの方は以下の記事を参照して下さい。

deviseでストロングパラメータを追加する (Rails5を受けての変更点)

ログイン用のパラメーターの設定

emailとusernameのどちらでもログイン出来るようにするためのパラメーター:loginを設定します。

loginパラメーターのアクセサを設定

カラムに存在しないフィールドをフォーム内で設定すると通常では利用することが出来ません。そのため、attr_accessorを定義することで利用可能にします。

app/models/user.rb
  attr_accessor :login

ログイン認証用のキーをloginに設定

先ほど、confirmableを追記したところにさらに:authentication_keys => [:login]を追加します。

app/models/user.rb
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :trackable, 
         :validatable, :confirmable, :authentication_keys => [:login]

ログイン認証の条件をオーバーライド

以下を追記します。ここではdeviseのログイン認証のメソッドをオーバーライド(上書き)することで新たなログイン条件に設定しています。
ここでは、usernameとemailのどちらでもログイン出来るように条件を設定しています。

app/models/user.rb
# 以下を追加
  def self.find_first_by_auth_conditions(warden_conditions)
    conditions = warden_conditions.dup
    if login = conditions.delete(:login)
      where(conditions).where(["username = :value OR lower(email) = lower(:value)", { :value => login }]).first
    else
      where(conditions).first
    end
  end

追記 2019.01.23

@takotakot さんより

lower(email) = lower(:value)" のところ、パフォーマンスを考えると気になります。
1. DB に保存するときに lower して保存する
2. lower してあるカラムを追加する
3. 何も与えないで比較させる
等工夫した方が良さそうです。full scan になってしまうので。

usernameのバリデーションをかける

usernameのバリデーションがない状態なので最低限のバリデーションはつけておきましょう。
下記を追加することでusernameがuniqueであることと、文字数が4から20文字であることを設定出来ます。(case_sensitive: :falssは大文字小文字の区別をしないということ)
こちらは各自お好みの設定をすればオーケーです。

app/models/user.rb
# 以下を追加
validates :username,
  uniqueness: { case_sensitive: :false },
  length: { minimum: 4, maximum: 20 }

半角英数字のバリデーションをつけるのも良さそうですね。

format: { with: /\A[a-z0-9]+\z/, message: "ユーザー名は半角英数字です"}

追記 2019.01.25

@takotakot さんより

attr_accessor :loginを予期するということは、フォーム側で、name や email ではなく、login として情報送信を試みるはずなのですが、その部分の説明が抜け落ちています。
参考にされている記事の「Viewファイルの書き換え」というところに書いてあります。

もし、View周りでお困りの場合はこちらもご参照ください。Rails4.1でdeviseのログインIDをemailとusernameのどちらでも可能にする

参考

Rails4.1でdeviseのログインIDをemailとusernameのどちらでも可能にする
後半部分はこちらを参考にしています。

shizuma
web&DeepLearningエンジニア。 ACES.inc←東京大学大学院/東京←鹿児島/blog https://blog.seishin55.com ; Qiita https://qiita.com/shizuma ; note https://note.mu/seishin55
https://seishin55.com
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした