【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です
![](https://blog.sawa-works.com/wp-content/uploads/2022/12/ea6aece36d2acb06e9df541b270cb224.png)
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
ここでエラーが発生しました。
![](https://blog.sawa-works.com/wp-content/uploads/2022/12/a3bf88172a8d352f40f4226ab3d8162d.png)
赤線部のところ「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' }
再チャレンジ・・・うまくいきました。
![](https://blog.sawa-works.com/wp-content/uploads/2022/12/e2d745327927df5b1edac2f0b046259f.png)
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とか諸々ができないので必須です。
![](https://blog.sawa-works.com/wp-content/uploads/2022/12/devise-token.png)
ここまでの作業で、単純な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が使われる点です。
最初、ちょっと躓いたので、忘れないためのメモでした。
おわり