Rails
active_model_serializers
devise_token_auth

devise_token_authで複数認証するとアクセストークンが返ってこなかった

Railsでdevise_token_authを使ってAPIの認証機能を作っているときに、User以外のモデル(例えばAdminなど)を追加したらレスポンスヘッダーにアクセストークン等がなくてハマった話。


環境


最初の認証モデルを作成

まず最初にactive_model_serializersをインストールして使える状態にした。

続いて、devise_token_authをインストールしてUserモデルを作成した。

$ rails g devise_token_auth:install User user

$ rails db:migrate


config/routes.rb

mount_devise_token_auth_for 'User', at: 'user'


だいだいこんな感じでUserモデルの認証機能ができるので、適当にUserを作成してアクセストークンを取得してみる。

User.create!(email: "hoge@hoge.com", password: "password")

$ curl -D - -X "POST" "http://localhost:3000/user/sign_in" -H "Content-Type: application/json" -d $'{"email": "hoge@hoge.com", "password": "password"}'

HTTP/1.1 200 OK
X-Frame-Options: xxxxxxxxxxxxxxxxxx
X-XSS-Protection: xxxxxxxxxxxxxxxxxx
X-Content-Type-Options: xxxxxxxxxxxxxxxxxx
X-Download-Options: xxxxxxxxxxxxxxxxxx
X-Permitted-Cross-Domain-Policies: xxxxxxxxxxxxxxxxxx
Referrer-Policy: xxxxxxxxxxxxxxxxxx
Content-Type: application/json; charset=utf-8
access-token: xxxxxxxxxxxxxxxxxx
token-type: xxxxxxxxxxxxxxxxxx
client: xxxxxxxxxxxxxxxxxxx
expiry: xxxxxxxx
uid: hoge@hoge.com
Vary: xxxxxxxxxxxxxxxxxx
ETag: xxxxxxxxxxxxxxxxxx
Cache-Control: xxxxxxxxxxxxxxxxxx
X-Request-Id: xxxxxxxxxxxxxxxxxx
X-Runtime: xxxxxxxxxxxxxxxxxx
Transfer-Encoding: xxxxxxxxxxxxxxxxxx

ログインが成功して作成したUserのaccess-tokentoken-typeclientexpiryuidをレスポンスヘッダーに含まれるので、以降はこれらをリクエストヘッダーに入れてアクセスする感じ。


続いて管理者用のモデルを追加してみる

devise_token_authは複数の認証モデルもサポートしているので、新しく管理者用のモデルとしてAdminモデルを追加みてみる。

$ rails g devise_token_auth:install Admin admin

$ rails db:migrate


config/routes.rb

mount_devise_token_auth_for 'User', at: 'user'

mount_devise_token_auth_for 'Admin', at: 'admin'

だいだいこんな感じでAdminモデルの認証機能ができるので、適当にAdminを作成してアクセストークンを取得してみる。

Admin.create!(email: "admin@hoge.com", password: "password")

$ curl -D - -X "POST" "http://localhost:3000/admin/sign_in" -H "Content-Type: application/json" -d $'{"email": "admin@hoge.com", "password": "password"}'

HTTP/1.1 200 OK
X-Frame-Options: xxxxxxxxxxxxxxxxxx
X-XSS-Protection: xxxxxxxxxxxxxxxxxx
X-Content-Type-Options: xxxxxxxxxxxxxxxxxx
X-Download-Options: xxxxxxxxxxxxxxxxxx
X-Permitted-Cross-Domain-Policies: xxxxxxxxxxxxxxxxxx
Referrer-Policy: xxxxxxxxxxxxxxxxxx
Content-Type: application/json; charset=utf-8
Vary: xxxxxxxxxxxxxxxxxx
ETag: xxxxxxxxxxxxxxxxxx
Cache-Control: xxxxxxxxxxxxxxxxxx
X-Request-Id: xxxxxxxxxxxxxxxxxx
X-Runtime: xxxxxxxxxxxxxxxxxx
Transfer-Encoding: xxxxxxxxxxxxxxxxxx

なんてこった、access-tokentoken-typeclientexpiryuidがない。

さっきのUserではあったのに、いったい何故・・・


解決策

stackoverflowに同じような事例があった。

どうもactive_model_serializerの影響っぽい。

ソースコードあんまり読んでないけど、ここで指定しているscopeがcurrent_userになってるから?

とりあえず回答にあるように、devise_token_authのSessionsControllerをoverrideしたらちゃんとアクセストークンが返ってきた。


app/controllers/admin/sessions_controller.rb

def render_create_success

render json: {
data: resource_data(resource_json: @resource.token_validation_response)
}, scope: :current_admin
end

active_model_serializerが影響してるとは思わなかったのでハマってしまったが、もっと良い解決策がないかもう少し調査が必要かも・・・



追記

overrideするよりapplication_controllerに以下を追加する方法のほうが良さそう。


application_controller.rb

serialization_scope :view_context