LoginSignup
13
10

More than 5 years have passed since last update.

Rails5でdeviseをAmazon SES SMTPサーバーを使って動かす

Last updated at Posted at 2018-12-21

はじめに

Rails5でdeviseを使う記事はほとんどがGmailをSMTPサーバーにしているが、SESで動くようにしたので、後で忘れないようにその時の作業記録を残す。

環境

Rails 5.2.2
Ruby 2.5.3
MySQL 5.6

deviseとは

参考記事より引用

ユーザー登録して、送られてきたメールのリンクをクリックして本登録して、ログインして、パスワード忘れたら再設定して、何回もログインミスったらアカウントロックして…などといった認証系アプリに必要な機能を簡単に追加できる便利なgemです。

Gemのインストール

1. Rails Projectの作成

私はRubyMineを使っているので新規Applicationプロジェクトを作成。これは説明不要と思うが、要するに以下のコマンド。

$ rails new devise_test
$ cd devise_test

2. gemファイルの編集とインストール

Gemfileに以下を追記してgemをインストールする。SESの認証情報をコードに書きたく無いのでdotenvを使う。

Gemfile
# devise
gem 'devise'
gem 'dotenv'
インストール
$ bundle install
devise関連ファイルを追加
$ rails g devise:install

rails g devise:install が動かない時はこの記事を参照。私の時はspringの停止で解決した。
http://ohara.geniusroots.com/entry/2017/03/26/145253

出力されるメッセージ
Running via Spring preloader in process 43961
      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

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

3. deviseの設定

上記のメッセージにいろいろ書いてあるが、今回は自分のMacでRailsサーバーを動かすので英文の例に書いてあったconfig.action_mailer.default_url_options = { host: 'localhost', port: 3000 }を以下のファイルに追加。

config/environments/development.rb
以下を一番下の end の手前に追記
# mailer setting
config.action_mailer.default_url_options = { host: 'localhost', port: 3000 }

4. ページの作成

現状ページは1つも作っていないため、 Topページにアクセスした際に表示されるページを作成。rootだけは特別で / ではなく # を使うことに注意。

$ rails g controller Pages index show
config/routes.rb
Rails.application.routes.draw do
  root 'pages#index'  ここのgetrootにして/#に変更。
  get 'pages/show'
  # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
end

5. flashメッセージの設定

参考記事そのまま

ログインした時などに上の方に「ログインしました」みたいなメッセージが出るようにします。以下のファイルの<body>タグのすぐ下に指定されたタグを挿入します。

app/views/layouts/application.html.erb
<!DOCTYPE html>
<html>
  <head>
    <title>DeviseTest2</title>
    <%= csrf_meta_tags %>
    <%= csp_meta_tag %>

    <%= stylesheet_link_tag    'application', media: 'all', 'data-turbolinks-track': 'reload' %>
    <%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %>
  </head>

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

    <%= yield %>
  </body>
</html>

6. DeviseのViewを生成

これも参考記事そのまま。

Deviseの導入で追加されるViewは、以下のコマンドを実行しなければデザインを変更できないので、デザインをカスタマイズするためにも実行します。

$ rails g devise:views

いろいろファイルが作成されます。

7. モデルの作成

以下を実行してmigrationファイルを作る。

$ rails g devise User

8. Userモデル

deviseの機能を全部使いたいので、trackable, confirmable, lockable, timeoutable をモデルに追加。

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,
         :trackable, :confirmable, :lockable, :timeoutable
end

9. マイグレーションファイルの編集

deviseの機能を全部使いたいので、上記7.でできたマイグレーションファイルのコメントを8.のモデルに合わせて該当部分を外す。

db/migrate/20181220121754_devise_create_users.rb
# frozen_string_literal: true

class DeviseCreateUsers < ActiveRecord::Migration[5.2]
  def change
    create_table :users do |t|
      ## Database authenticatable
      t.string :email,              null: false, default: ""
      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.string   :current_sign_in_ip
      t.string   :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

アカウント名を保存しておきたいのでusernameもUserテーブルに追加します。参考記事ではTwitter認証もやっているのでprovider, uidもそのまま入れてますが、ここでは使いません。

$ rails g migration add_columns_to_users provider uid username

以下のようなマイグレーションファイルができます。

class AddColumnsToUsers < ActiveRecord::Migration[5.2]
  def change
    add_column :users, :provider, :string
    add_column :users, :uid, :string
    add_column :users, :username, :string
  end
end

10. DBの作成とマイグレーション

ここまで来たら、config/database.ymlを環境に合わせて設定してDBを作成してからマイグレーションしてテーブルを作成します。

$ rake db:create
$ rake db:migrate

11. viewの編集

参考記事そのままです。

以下のファイルを編集して、ページ上部にメニューが出るようにします。
user_signed_in?はdeviseのHelperメソッドです。
ログインしているかしてないかで上部のメニューの表示が変わるようになります。
current_userで現在サインインしているユーザーの情報を取得できます。

app/views/layouts/application.html.erb
<!DOCTYPE html>
<html>
  <head>
    <title>DeviseTest2</title>
    <%= csrf_meta_tags %>
    <%= csp_meta_tag %>

    <%= stylesheet_link_tag    'application', media: 'all', 'data-turbolinks-track': 'reload' %>
    <%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %>
  </head>

  <body>
    <header>
     <nav>
        <% if user_signed_in? %>
          <strong><%= link_to current_user.username, pages_show_path %></strong>
          <%= link_to 'プロフィール変更', edit_user_registration_path %>
         <%= link_to 'ログアウト', destroy_user_session_path, method: :delete %>
        <% else %>
          <%= link_to 'サインアップ', new_user_registration_path %>
          <%= link_to 'ログイン', new_user_session_path %>
        <% end %>
      </nav>
     </header>

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

    <%= yield %>
  </body>
</html>

以下の2ページも修正。
indexの方がトップページ、showの方がログインしているユーザー用のページにします。

app/views/pages/index.html.erb
<h1>ようこそ</h1>
<p>トップページです。</p>
app/views/pages/show.html.erb
<h1>こんにちは、<%= current_user.username %>さん</h1>
<p>ユーザー用ページです。</p>

12. 動作確認

ここでRailsサーバーを起動して動作確認します。

$ rails server

http://localhost:3000 をブラウザで開いてTopページを確認。

DeviceでSESを使ったメール送信の設定

次にSESのSMTPサーバーを使ってメール送信する設定をします。

1. development.rbの下記をコメントアウト。

33行目あたり。

config/environments/development.rb
# Don't care if the mailer can't send.
#config.action_mailer.raise_delivery_errors = false

2. メール送信者の設定

config/initializers/devise.rbのconfig.mailer_senderを、
SES に登録されているメールアドレスに書き換えます。例えば、customer_support<noreply@hogehoge.com>という感じ。私の場合は hogehoge.com は自分が管理しているドメイン名で、SESに登録しているものを使いました。noreplyの所は何を入れても動くようです。

21行目あたり

config/initializers/devise.rbのconfig.mailer_sender
#config.mailer_sender = 'please-change-me-at-config-initializers-devise@example.com'
  config.mailer_sender = 'customer_support<reply@hogehoge.com>'

development.rbに下記を記述。

config/environment/development.rb
Rails.application.configure do
  require 'dotenv' ⑧

# 途中省略

# derault url
  config.action_mailer.default_url_options = { host: 'localhost', port: 3000 }  ①

# Amazon SES smtp setting
  Dotenv.load 
  config.action_mailer.delivery_method = :smtp
  config.action_mailer.smtp_settings = {
      :address => 'email-smtp.us-east-1.amazonaws.com', ②
      :port => 587, ⑦
      :authetication => :login,
      :user_name => ENV['SES_SMTP_USER'], ③
      :domain => 'hogehoge.com',  ④
      :password => ENV['SES_SMTP_PASSWORD'], ⑤
      :enable_starttls_auto => true ⑥
  }

設定項目の説明

①自分のMacでRailsサーバーを動かすのでlocalhost:3000に合わせて設定。
②自分が使っているAmazonのRegionに合わせて、以下を参照してAmazon SES SMTP エンドポイントを記述。

SES参考リンク: Amazon SES SMTP エンドポイントへの接続

③下記のSESの説明Amazon SES コンソールを使用した Amazon SES SMTP 認証情報の取得を参考にして、SMTP認証情報を作り、そこで作られたSMTPユーザー名を入れる。例えばAKIXXXX9999XXXXEAのような文字列になる。コードに認証情報を書きたくないので dotenvを使った。

SES参考リンク: Amazon SES SMTP 認証情報の取得

④ドメイン名は自分が使いたいドメインでSESに登録しているものを指定。
⑤同様にSMTPパスワードを入れる。例えばAxHYujHOGEhoge999hYuHogeのような文字列になる。
⑥STARTTLSを使うのでtrue

STARTTLS とは、暗号化されていない接続を暗号化された接続にアップグレードする方法です。STARTTLS には、様々なプロトコルに対応したバージョンがあります。SMTP バージョンは、「RFC 3207」に定義されています。

STARTTLS 接続を設定する場合、SMTP クライアントは、ポート 25、587、または 2587 で Amazon SES SMTP エンドポイントに接続し、EHLO コマンドを発行します。次に、サーバーから STARTTLS SMTP 拡張機能をサポートしているという通知が来るのを待ちます。通知を受けたクライアントは、STARTTLS コマンドを発行し、TLS ネゴシエーションを開始します。ネゴシエーションが完了すると、クライアントが暗号化された新しい接続で EHLO コマンドを発行し、SMTP セッションが正常に進行します。

⑦STARTTLSを使うので587番ポートを指定。
⑧これを入れないとdotenv③と④を入れてくれないのでロードする。

動作確認

ここまで来たらメールが飛ぶはずなので、動作確認してみる。

サインアップすると次のメールが届いた!

Confirmation instructions

Customer_support <noreply@hogehoge.com> <=config.mailer_senderで設定した送信元アドレス

Welcome サインアップしたメールアドレス!

You can confirm your account email through the link below:

Confirm my account

メールで送られる文章は以下のファイルを編集することで可能です。
app/views/devise/mailer/confirmation_instructions.html.erb

ユーザー名の追加

上記の動作確認でメールが飛ぶことは確認した。しかし、usernameメソッドは未定義なのでログイン後にNoMethodErrorが発生する。なのでユーザー名を入れられるようにする。

1. ユーザー名を入力するエリアを追加

参考記事のままエリアを追加。

サインアップページにユーザー名を入力するエリアを追加します。ユーザーのプロフィール変更ページ(views/devise/registrations/edit.html.erb)にも同様に追加しときます。

views/devise/registrations/newt.html.erb
<h2>Sign up</h2>

<%= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %>
  <%= devise_error_messages! %>

  <div class="field">
    <%= f.label :username %><br />
    <%= f.text_field :username %>
  </div>

  <div class="field">
    <%= f.label :email %><br />
    <%= f.email_field :email, autofocus: true, autocomplete: "email" %>
  </div>

  <div class="field">
    <%= f.label :password %>
    <% if @minimum_password_length %>
    <em>(<%= @minimum_password_length %> characters minimum)</em>
    <% end %><br />
    <%= f.password_field :password, autocomplete: "new-password" %>
  </div>

  <div class="field">
    <%= f.label :password_confirmation %><br />
    <%= f.password_field :password_confirmation, autocomplete: "new-password" %>
  </div>

  <div class="actions">
    <%= f.submit "Sign up" %>
  </div>
<% end %>

<%= render "devise/shared/links" %>
views/devise/registrations/edit.html.erb
<h2>Edit <%= resource_name.to_s.humanize %></h2>

<%= form_for(resource, as: resource_name, url: registration_path(resource_name), html: { method: :put }) do |f| %>
  <%= devise_error_messages! %>

  <div class="field">
    <%= f.label :username %><br />
    <%= f.text_field :username %>
  </div>

  <div class="field">
    <%= f.label :email %><br />
    <%= f.email_field :email, autofocus: true, autocomplete: "email" %>
  </div>

  <% if devise_mapping.confirmable? && resource.pending_reconfirmation? %>
    <div>Currently waiting confirmation for: <%= resource.unconfirmed_email %></div>
  <% end %>

  <div class="field">
    <%= f.label :password %> <i>(leave blank if you don't want to change it)</i><br />
    <%= f.password_field :password, autocomplete: "new-password" %>
    <% if @minimum_password_length %>
      <br />
      <em><%= @minimum_password_length %> characters minimum</em>
    <% end %>
  </div>

  <div class="field">
    <%= f.label :password_confirmation %><br />
    <%= f.password_field :password_confirmation, autocomplete: "new-password" %>
  </div>

  <div class="field">
    <%= f.label :current_password %> <i>(we need your current password to confirm your changes)</i><br />
    <%= f.password_field :current_password, autocomplete: "current-password" %>
  </div>

  <div class="actions">
    <%= f.submit "Update" %>
  </div>
<% end %>

<h3>Cancel my account</h3>

<p>Unhappy? <%= button_to "Cancel my account", registration_path(resource_name), data: { confirm: "Are you sure?" }, method: :delete %></p>

<%= link_to "Back", :back %>

2. ApplicationControllerに以下を追加

参考記事そのまま

次にApplicationControllerに以下を追加します。
テンプレートを変えて、ユーザー名を入力するようにしただけでは実際に登録されないからです。
詳しくはstrong_parametersについて調べてください。
簡単に言えばよく分からんパラメーターは渡せないようになってるので渡せるようにします。

app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  protect_from_forgery with: :exception
  before_action :configure_permitted_parameters, if: :devise_controller?

  def after_sign_in_path_for(resource)
    pages_show_path
  end

  private
  def sign_in_required
    redirect_to new_user_session_url unless user_signed_in?
  end

  protected
  def configure_permitted_parameters
    devise_parameter_sanitizer.permit(:sign_up, keys: [:username])
    devise_parameter_sanitizer.permit(:account_update, keys: [:username])
  end
end

メールアドレスの確認欄の追加

メールアドレスを2回入力させて、一致するかどうかのValidationを追加したい要件はよくある話だと思うので実装した。

1. email_confirmationの追加

app/views/devise/registrations/new.html.erb
<h2>Sign up</h2>

<%= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %>
  <%= devise_error_messages! %>

  <div class="field">
    <%= f.label :email, "Email" %><br />
    <%= f.email_field :email, autofocus: true, autocomplete: "email" %>
  </div>

  <div class="field">
    <%= f.label :email, "Email (Confirmation)" %><br />
    <%= f.email_field :email_confirmation, autofocus: true, autocomplete: "email" %>
  </div>
以下省略

2. application_controller.rbにemail_confirmationを追加

app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  protect_from_forgery with: :exception
  before_action :configure_permitted_parameters, if: :devise_controller?

  protected
  def configure_permitted_parameters
    devise_parameter_sanitizer.permit(:sign_up, keys: [:username])
    devise_parameter_sanitizer.permit(:sign_up, keys: [:email_confirmation])
    devise_parameter_sanitizer.permit(:account_update, keys: [:username])
  end
end

3. user.rbモデルにvalidationを追加

app/models/user.rb
class User < ApplicationRecord
    途中省略
  validates :email, confirmation: true
end

これで登録画面でメールアドレスの確認欄が追加されます。

FlashメッセージをかっこよくPopupにする。

defaultのDeviseだとnoticeがそのまま表示されて、ちょっとかっこ悪いのでJavaScriptでPopupさせるようにした。参考記事そのままで使えるようになった。

1. Gemを追加

/Gemfile
gem 'toastr-rails'
$ bundle install

2. ファイルに読み込ませるように追加

app/stylesheets/application.css
 *= require_tree .
 *= require_self
 *= require toastr
app/javascripts/application.js
//= require rails-ujs
//= require activestorage
//= require jquery
//= require bootstrap-sprockets
//= require toastr
//= require jquery_ujs
//= require turbolinks
//= require_tree .

3. appの全体に表示させるために下記ファイルに定義

app/views/layouts/application.html.erb
    <p class="notice"><%= notice %></p>  この行を削除して以下を追加
     <p class="alert"><%= alert %></p>        この行も削除

    <!– flashメッセージを表示させる –>   
    <% unless flash.empty? %>
    <script type="text/javascript">
        <% flash.each do |f| %>
        <% type = f[0].to_s.gsub('alert','error').gsub('notice','info') %>
        toastr['<%= type %>']('<%= f[1] %>');
        <% end %>
    </script>
    <% end %>

4. gem-deviseの警告表示にも反映させる。

部分テンプレートとして使うために別ファイルを作成

参考記事の通りだとエラーになるのでdeviseの共有erbディレクトリ内にファイルを置いた。コードの中身は参考記事と同じです。

app/views/devise/shared/errormessages.html.erb
<% unless resource.errors.empty? %>
 <script type= "text/javascript">
  <% resource.errors.full_messages.each do |msg| %>
   toastr.error('<%= msg %>');
  <% end %>
 </script>
<% end %>

devise/confirmaitions/new.html.erb
devise/passwords/edit.html.erb
devise/passwords/new.html.erb
devise/registrations/edit.html.erb
devise/registrations/new.html.erb
devise/unlocks/new.html.erb
上記ファイルにある、
<%= devise_error_messages! %> を

<%= render "devise/shared/errormessages" %> に置換。ここは参考記事の通りだとNoMethodエラーになるので修正した。

参考記事

https://qiita.com/cigalecigales/items/f4274088f20832252374
http://lina-marble.hatenablog.com/entry/2016/03/30/180622
https://qiita.com/MZ1TB0ZSoVfeX9L/items/23d0214852d40902323b

関連記事

DeviseのSign upとLog inをBootstrapでModalポップアップにカスタマイズする
https://qiita.com/NYC-Blue/items/d104431b90e717e62051

13
10
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
13
10