【Rails】Rails APIにdeviseを組み込む手順を整理

今回はRails & Reactアプリで、Rails APIに認証機能を実装するためdeviseを組み込んだ際の手順を整理してみました。

deviseをインストール

まずはdeviseとdevise_token_authをインストールします。

deviseはRailsで認証機能を簡単に実装できるようにしてくれるライブラリ、devise_token_authはdeviseを拡張してトークン認証してくれるライブラリになります。

RailsプロジェクトのGemファイルに以下を追記して保存します。

(Gemfile)

gem 'devise'
gem 'devise_token_auth'

ターミナルからインストールします。
今回はdocker環境での説明になります。

(ターミナル)

cd {dockerのルートディレクトリ}

// bundle install
docker-compose build

// deviseをインストール(railsプロジェクト名は今回はapi)
docker-compose run api rails g devise:install
→ apiの下に以下のファイルが作成される
create  config/initializers/devise.rb
create  config/locales/devise.en.yml

// devise_token_authをインストール(railsプロジェクト名は今回はapi)
docker-compose run api rails g devise_token_auth:install User auth
→ apiの下に以下のファイルが作成される(更新される)
create  config/initializers/devise_token_auth.rb
insert  app/controllers/application_controller.rb
gsub  config/routes.rb
create  db/migrate/yyyymmddhhmiss_devise_token_auth_create_users.rb
create  app/models/user.rb

ここまででインストールは終了です

deviseの各設定(ソース修正)

deviseのインストールでRailsプロジェクトにプログラムソースのテンプレートが出来上がってますので、適宜修正していきます。

routes.rb

ルーティングの変更です。
deviseインストールの結果、routes.rbにはルーティングコードが自動で追加されています。(gsub config/routes.rb)
各自のアプリケーション仕様でnamespaceを使っている場合は、それに準じて記述を変更します。

(config/routes.rb)

// before
Rails.application.routes.draw do
  mount_devise_token_auth_for 'User', at: 'auth'
  namespace :api do
    namespace :v1 do
      〜〜省略
    end
  end
end

// after
Rails.application.routes.draw do
  namespace :api do
    namespace :v1 do
      mount_devise_token_auth_for 'User', at: 'auth'
      〜〜省略
    end
  end
end

deviseの詳細設定

deviseインストール直後では最低限の機能が有効になっています。
今回はトラッキング情報(サインイン回数、サインイン時間、IPアドレス)を記録する機能を有効にしておきます。

(app/models/user.rb)

// before
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable

// after
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable, :trackable

トラッキング機能有効化に伴いマイグレーションファイルにもカラムを追加します。

(db/migrate/yyyymmddhhmiss_devise_token_auth_create_users.rb)

# 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

マイグレーションファイルを更新したら、ターミナルからマイグレートします。

(ターミナル)

docker-compose run api rails db:migrate

これでDBにusersテーブルが作成されます。

動作確認

アプリを実行します。(今回はdocker仕様)

(ターミナル)

docker-compose up

別にターミナルを起動して、curlでdeviseを動かしていきます。

(ターミナル)

// SignUp
curl localhost:3000/api/v1/auth -X POST -d '{"email":"test@example.com", "password":"password", "password_confirmation": "password"}' -H "content-type:application/json" -i

successになればOKです

devise_token_authの詳細設定

トークンの設定を見ていきます。

(config/initializeers/devise_token_auth.rb)

// リクエストごとにトークンを更新するかを設定
// コメントブロックされているので、それを解除して設定します
// 毎回トークンが変わると制御が複雑化するので今回はfalseにします
config.change_headers_on_each_request = false

// トークンの有効期間を設定
// コメントブロックされているので、それを解除して設定します
// 今回はデフォルトの2週間のままとします
config.token_lifespan = 2.weeks

// frontとのやりとりをする際のヘッダー名前を解決します
// コメントブロックされているので、それを解除して設定します
// 今回はデフォルトのままとします
config.headers_names = {:'access-token' => 'access-token',
                        :'client' => 'client',
                        :'expiry' => 'expiry',
                        :'uid' => 'uid',
                        :'token-type' => 'token-type' }

動作確認

SignInで動作確認します。
先ほどと同様にアプリを実行して、別ターミナルからcurlで実施します。

(ターミナル)

// SignIn
curl localhost:3000/api/v1/auth/sign_in -X POST -d '{"email":"test@example.com", "password":"password"}' -H "content-type:application/json" -i

ここでエラーが発生しました。

赤線部のところ「NoMethodError: undefined method `downcase'」がヒントになりそうです。
devise_token_authのIssueにこのエラーが掲載されているようです。
(参考)https://github.com/lynndylanhurley/devise_token_auth/issues/1540

追加で対応します。

(config/initializeers/devise_token_auth.rb)

config.headers_names = {:'access-token' => 'access-token',
                        :'client' => 'client',
                        :'expiry' => 'expiry',
                        :'uid' => 'uid',
                        :'token-type' => 'token-type',
                        :'authorization' => 'authorization' }

再チャレンジ・・・うまくいきました。

frontとの疎通設定(CORS)

今のままだとdevise_token_authからのresponse.headerでtoken、uid、clientなどが取得できないので、それを解決します。

既にフロントエンドとの疎通のためrack-corsは導入済みの前提で記載します。
cors.rbにdevise_token_authでやりとりする際のヘッダー情報を追記します。

(config/initializers/cors.rb)

// before
Rails.application.config.middleware.insert_before 0, Rack::Cors do
  allow do
    origins 'http://localhost:3001'
    resource '*',
      headers: :any,
      methods: [:get, :post, :put, :patch, :delete, :options, :head]
  end
end

// after
Rails.application.config.middleware.insert_before 0, Rack::Cors do
  allow do
    origins 'http://localhost:3001'
    resource '*',
      headers: :any,
      expose: ["access-token", "expiry", "token-type", "uid", "client"], 
      methods: [:get, :post, :put, :patch, :delete, :options, :head]
  end
end

これで、アプリで実際に観察してみると以下の赤線のようにdevise_token_authから応答が取得できる。
これがないとSignOutとか諸々ができないので必須です。

ここまでの作業で、単純なdevise認証は実装完了です。
あとは要求事項によって、必要な機能を追加すればOKです。

メール認証の実装

メール認証を実装してみたので、その時の手順と課題も書き留めておきます。

今回は、ローカル環境(docker)でメールサーバー等を考える手間を省く目的で、運用環境(運用サーバー)に構築してから、実装しました。

今回実装したメール認証は、SignUpで登録したメールアドレスにメールを送信して、そのメールで「Confirm」リンクをクリックすることで、アカウントが確定するというものです。
更に、メールアドレス変更時にメール認証するというのもあるようですが、今回は対象外にしています。

userテーブルにカラム追加

メール認証に必要なカラムをuserテーブルに追加します。
といっても、今のバージョンだとマイグレーションファイルにデフォルトで用意されているので、既に追加されているかもしれません。
念のため確認して、追加されてなければマイグレーションし直します。

(db/migrate/yyyymmddhhmiss_devise_token_auth_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

ちなみに一番したのカラムは、メールアドレス変更時のメール認証する場合のみ使用するカラムです。
今回は別になくても問題はありません。

userモデルにメール認証機能有効化

user.rbに以下のように追記します。(赤字部分)

(app/models/user.rb)

devise :database_authenticatable, :registerable,
       :recoverable, :rememberable, :validatable, :trackable, :confirmable

Front側のSignUpのパラメータを変更

API側にメール認証を有効化しても、フロントエンド側のSignUp処理でAPIパラメータを追加する必要があります。(以下、赤字)

(フロントエンドでSignUp処理のパラメータ例)

const params = {
  name: {画面で入力したname}, 
  email: {画面で入力したメールアドレス}, 
  password: {画面で入力したパスワード}, 
  passwordConfirmation: {画面で入力した確認用パスワード},
  confirmSuccessUrl: "{メール認証成功時のリダイレクト先URL}",
}

deviseにメール設定

RailsAPI上で考慮するメール設定は大きく2つ。
1つはRails自体のメール設定でこれがベースになります。
もう1つはdeviseのメール設定で、イメージとしてはdeviseメール(送信元情報とか本文とか)をRailsメール設定(送信の仕組み)を通して送信するよ・・・という感じになります。

ここはdeviseのメール設定になります。

(config/initializers/devise.rb)

// ここは送信元のアドレス設定なので任意の設定
// 実は今回の方法では意味をなさなかったが、記述は必要だと思います。
config.mailer_sender = 'devise@example.com'

// deviseのメーラーを有効化
// デフォルトだとコメントブロックされていたので、コメントを外します。
config.mailer = 'Devise::Mailer'

Railsのメール設定(失敗談)

まずは今回の失敗例です。

私の運用サーバにはメール送信専用としてPostfixが導入されています。
最初はこのメールサーバを使って、deviseメールを送信しようと考えました。

(config/environments/production.rb)

# action_mailerを追記
config.action_mailer.raise_delivery_errors = true
config.action_mailer.default_url_options = { :host => '{サイトのドメイン}' }
config.action_mailer.delivery_method = :smtp
config.action_mailer.smtp_settings = {
  :address => '{メールサーバのドメイン}'
}

これでテストしたところ、特にエラーは出ないものの、指定先(Gmail)にメールが送られて来ません。
Postfixのログを確認したところ以下のようなメッセージが見られます。

status=bounced・・・Our system has detected that this message is 550-5.7.1 likely unsolicited mail. To reduce the amount of spam sent to Gmail, 550-5.7.1 this message has been blocked.https://support.google.com/mail/?p=UnsolicitedMessageError 550 5.7.1  for more information.・・・

いろいろ調べたところメールの信頼性的なところでブロックされているようです。

メールの信頼性を改善するのは次回課題として、今回は別な方法を選択します。

Railsのメール設定(成功談)

今回はGmailのSMTPを使ってメール送信します。

Gmailのアプリパスワードを取得

GmailのSMTPを使うにあたっては、準備として、使用するメールアカウントのアプリパスワードを取得する必要があります。

取得方法は解説省略します。

(参考)https://labo.kon-ruri.co.jp/rails-send-mail-via-gmail-smtp/

Railsの設定

(config/environments/production.rb)

# action_mailerを追記
config.action_mailer.raise_delivery_errors = true
config.action_mailer.default_url_options = { :host => '{サイトのドメイン}' }
config.action_mailer.delivery_method = :smtp
config.action_mailer.smtp_settings = {
  address: 'smtp.gmail.com',
  domain: 'gmail.com',
  port: 587,
  user_name: '{使用するGmailアドレス}',
  password: '{取得したアプリパスワード}',
  authentication: :login
}

以上の方法で、無事メールが届くようになりました。

もう1歩の調整(失敗談)

上記の方法で認証のメールは届きましたが、メールに記されている「Confirm my account」リンクをクリックしても、アカウントが認証されないという現象がありました。

仕様通りであれば、「Confirm my account」リンクをクリックすると、userテーブルの「confirmed_at」カラムに日時がセットされて認証完了ということなるはずでした。
「Confirm my account」リンクをクリックしても、userテーブルは更新されず、でも目的の画面にリダイレクトはされるので、UI上では正常に見えます。

原因と解決

原因

リンク先のURLを観察すると、接続先のURL(リダイレクト先ではありません)に違和感がありました。

誤 http://〜〜〜〜〜
正 https://〜〜〜〜〜

URLを明示的に設定したところは思い当たりませんでしたが、Railsのメール設定でサイトドメインを設定した個所が怪しいと考えました。

解決

以下、記述を変更しました。

// before
config.action_mailer.default_url_options = { :host => '{サイトのドメイン}' }

// after
config.action_mailer.default_url_options = { protocol: 'https', host: '{サイトのURL(https://〜〜〜)}' }

再度、SignUpからやり直し、送信されてきたメールで「Confirm my account」リンクを踏んで確認。
リダイレクトも正常。
userテーブルの「confirmed_at」カラムに日時が無事セットされ、そのアカウントで無事SignInできるようになりました。

おまけ

メール送信の確認(action_mailerの記述確認)する際には、Rails標準のメーラーを作成して、Railsコンソールから実施すると確認が楽でした。

注意点としては、Railsコンソールから試す場合は、production.rbではなく、development.rbが使われる点です。
最初、ちょっと躓いたので、忘れないためのメモでした。

おわり

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です