ruby-on-railsdeviserails-api

Devise Token Auth - versioned API: uninitialized constant ApplicationController


I am integrating Devise Token Auth into my versioned Rails 5 API. Here is the structure:

Gemfile:

source 'https://rubygems.org'
...
gem 'omniauth', '~> 1.3'
gem 'devise_token_auth', '~> 0.1.38'
...

routes.rb:

require "api_constraints"

Rails.application.routes.draw do

  namespace :api, defaults: { format: :json } do
    scope module: :v1, constraints: ApiConstraints.new(version: 1, default: true) do
      mount_devise_token_auth_for 'User', at: 'auth'
      resources :users
    end
  end
end

application_controller.rb (app/controllers/api/v1/):

module Api
  module V1
    class ApplicationController < ActionController::API
      include ActionController::Serialization
      include DeviseTokenAuth::Concerns::SetUserByToken
    end
  end
end

When I try to POST:

{
    "email": "testuser@domain.com",
    "password": "testuserpassword"
}

to /api/auth/sign_in, I get the error ActionController::RoutingError (uninitialized constant ApplicationController).

It seems that the devise_controller doesn't have access to ApplicationController when it tries to call its own methods. So I tried setting the base controller:

mount_devise_token_auth_for 'User', at: 'auth', base_controller: 'Api::V1::ApplicationController'

That also didn't work.

To fix the issue, I could remove the module separation in application_controller.rb to make it:

class ApplicationController < ActionController::API
    include ActionController::Serialization
    include DeviseTokenAuth::Concerns::SetUserByToken
end

but that of course causes a different load error (because things are no longer scoped correctly): LoadError (Unable to autoload constant Api::V1::ApplicationController, expected /home/ubuntu/workspace/app/controllers/api/v1/application_controller.rb to define it).

The only thing that actually works is to not have it scoped in a namespace/module at all:

Rails.application.routes.draw do

  mount_devise_token_auth_for 'User', at: 'auth'

  namespace :api, defaults: { format: :json } do
    scope module: :v1,
                  constraints: ApiConstraints.new(version: 1, default: true) do
      resources :users
    end
  end
end

but that defeats the whole purpose of versioning. Am I missing something?


Solution

  • I don't have that fancy default scoping happening, but this works for me:

    Rails.application.routes.draw do
      namespace :api do
        scope :v1 do
          mount_devise_token_auth_for 'User', at: 'auth'
        end
      end
    end
    

    A big difference is that in addition to my normal scoped-by-version ApplicationController, I have an ApplicationController at app/controller/application_controller.rb that reads:

    class ApplicationController < ActionController::API
      include DeviseTokenAuth::Concerns::SetUserByToken
    end
    

    This may not be ideal, but it works with how DeviseTokenAuth is currently designed. A proper fix would likely involve a change inside of DeviseTokenAuth itself.

    Hopefully this is helpful. With the above code, I have the following route that works to do what I need it to do:

    api_user_session POST /api/v1/auth/sign_in(.:format) devise_token_auth/sessions#create

    . . . and I don't get the 'different load error' you mentioned when you attempted a similar solution.