Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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": "[email protected]",
    "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?

like image 778
Chris Cirefice Avatar asked Jun 13 '26 04:06

Chris Cirefice


2 Answers

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.

like image 136
Ecnalyr Avatar answered Jun 16 '26 07:06

Ecnalyr


Devise tries to make its built-in controllers inherit from your own ApplicationController, but in your app that class doesn't exist. Instead you have an Api::V1::ApplicationController class.

Fortunately you can configure Devise to inherit from something else. In your config/initializers/devise.rb, do this:

config.parent_controller = 'Api::V1::ApplicationController'
like image 42
Paul A Jungwirth Avatar answered Jun 16 '26 06:06

Paul A Jungwirth