lynndylanhurley / devise_token_auth

Token based authentication for Rails JSON APIs. Designed to work with jToker and ng-token-auth.
Do What The F*ck You Want To Public License
3.54k stars 1.14k forks source link

Filter chain halted as :authenticate_user! rendered or redirected #603

Closed unDeveloper closed 8 years ago

unDeveloper commented 8 years ago

I'm developing a json API with rails, using rails-api gem, devise_token_auth and angularjs. Using the standard configuration all seems to be ok until I active the line of:

before_action :authenticate_user!

From that point my app only show me the next error: Filter chain halted as :authenticate_user! rendered or redirected

I was reading that this seem caused about a missing header on the request, but i couldn't figure out what i am doing wrong.

This is my configuration until now: Gemfile

source 'https://rubygems.org'

gem 'rails', '4.2.6'

gem 'rails-api'

gem 'spring', :group => :development

gem 'mysql2'

gem 'devise'
gem 'devise_token_auth'
gem 'omniauth'

gem 'rack-cors', :require => 'rack/cors'

application.rb

require File.expand_path('../boot', __FILE__)

require 'rails/all'

# Require the gems listed in Gemfile, including any gems
# you've limited to :test, :development, or :production.
Bundler.require(*Rails.groups)

module SlsApiTest
  class Application < Rails::Application
    # Settings in config/environments/* take precedence over those specified here.
    # Application configuration should go into files in config/initializers
    # -- all .rb files in that directory are automatically loaded.

    # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone.
    # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC.
    # config.time_zone = 'Central Time (US & Canada)'

    # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded.
    # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s]
    # config.i18n.default_locale = :de

    # Do not swallow errors in after_commit/after_rollback callbacks.
    config.active_record.raise_in_transactional_callbacks = true
    config.middleware.use Rack::Cors do
      allow do
        origins '*'
        resource '*',
          :headers => :any,
          :expose  => ['access-token', 'expiry', 'token-type', 'uid', 'client'],
          :methods => [:get, :post, :options, :delete, :put, :patch]
      end
    end
  end
end

application_controller.rb

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

  before_action :authenticate_user!
end

devise_token_auth.rb

DeviseTokenAuth.setup do |config|
  # By default the authorization headers will change after each request. The
  # client is responsible for keeping track of the changing tokens. Change
  # this to false to prevent the Authorization header from changing after
  # each request.
  # config.change_headers_on_each_request = true

  # By default, users will need to re-authenticate after 2 weeks. This setting
  # determines how long tokens will remain valid after they are issued.
  # config.token_lifespan = 2.weeks

  # Sets the max number of concurrent devices per user, which is 10 by default.
  # After this limit is reached, the oldest tokens will be removed.
  # config.max_number_of_devices = 10

  # Sometimes it's necessary to make several requests to the API at the same
  # time. In this case, each request in the batch will need to share the same
  # auth token. This setting determines how far apart the requests can be while
  # still using the same auth token.
  # config.batch_request_buffer_throttle = 5.seconds

  # This route will be the prefix for all oauth2 redirect callbacks. For
  # example, using the default '/omniauth', the github oauth2 provider will
  # redirect successful authentications to '/omniauth/github/callback'
  # config.omniauth_prefix = "/omniauth"

  # By default sending current password is not needed for the password update.
  # Uncomment to enforce current_password param to be checked before all
  # attribute updates. Set it to :password if you want it to be checked only if
  # password is updated.
  # config.check_current_password_before_update = :attributes

  # By default, only Bearer Token authentication is implemented out of the box.
  # If, however, you wish to integrate with legacy Devise authentication, you can
  # do so by enabling this flag. NOTE: This feature is highly experimental!
  # enable_standard_devise_support = false
end

devise.rb

Devise.setup do |config|
  # The e-mail address that mail will appear to be sent from
  # If absent, mail is sent from "please-change-me-at-config-initializers-devise@example.com"
  config.mailer_sender = "contacto@hunabsys.com"

  # If using rails-api, you may want to tell devise to not use ActionDispatch::Flash
  # middleware b/c rails-api does not include it.
  # See: http://stackoverflow.com/q/19600905/806956
  config.navigational_formats = [:json]
end

My angular app is literaly a copy and paste of the examples using in the repo for ng-token-auth, with index.html making registration of users and index2.html doing the login request.

This is the link for the application repo that i'm using for testing: APITest

Hope you can help me!

Thanks in advance =D

dukeimg commented 8 years ago

Having the same issue. Any progress?

unDeveloper commented 8 years ago

I'm still doing some testing, but it seems to be resolved modifying before_action :authenticate_user! to before_filter :authenticate_user!, except: [:new, :create]

If something new comes to surface I'll let you know

Regards!

farukca commented 8 years ago

Having same problem, did you find a solution ?

lucasloliveira commented 8 years ago

I'm having the same problem. I can login fine and even replay a few 'validate_token' requests, but at some point it will fail and give me 401 unauthorized.

unDeveloper commented 8 years ago

Can you post your configuration? How are you handle the request headers and response headers? Did you had any errors during those requests?

lucasloliveira commented 8 years ago

I'm using ng-token-auth to do all of the communication with the rails API. My configs are all default for now, following are the files I've changed:

application_controller.rb

class ApplicationController < ActionController::Base
  # Prevent CSRF attacks by using :null_session
  protect_from_forgery with: :null_session
  include DeviseTokenAuth::Concerns::SetUserByToken

...

user.rb

class User < ActiveRecord::Base
  # Include default devise modules.
  devise :database_authenticatable, :registerable,
          :recoverable, :rememberable, :trackable, :validatable,
          :confirmable, :omniauthable
  include DeviseTokenAuth::Concerns::User

...

Is there any other file that could be interfering with that?

Thank you for the impressively fast response 😃

unDeveloper commented 8 years ago

Well i have a little time before i go to sleep...

Back to the point... I'm assuming you've have installed and configured rack/cors for crossite requests when you configure devise_token_auth?

lucasloliveira commented 8 years ago

Yes! This is what my application.rb looks like:

require File.expand_path('../boot', __FILE__)

require 'rails/all'

# Require the gems listed in Gemfile, including any gems
# you've limited to :test, :development, or :production.
Bundler.require(*Rails.groups)

module RailsSports2
  class Application < Rails::Application
    # Settings in config/environments/* take precedence over those specified here.
    # Application configuration should go into files in config/initializers
    # -- all .rb files in that directory are automatically loaded.

    # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone.
    # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC.
    # config.time_zone = 'Central Time (US & Canada)'

    # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded.
    # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s]
    # config.i18n.default_locale = :de

    # Do not swallow errors in after_commit/after_rollback callbacks.
    config.active_record.raise_in_transactional_callbacks = true

    config.middleware.use config.session_store, config.session_options
    config.middleware.use Rack::MethodOverride
    config.middleware.use ActionDispatch::Cookies
    config.middleware.use ActionDispatch::Session::CookieStore
    config.middleware.use ActionDispatch::Flash

    config.autoload_paths += %W( #{config.root}/lib )

    config.middleware.use Rack::Cors do
      allow do
        origins '*'
        resource '*',
                 :headers => :any,
                 :expose  => ['access-token', 'expiry', 'token-type', 'uid', 'client'],
                 :methods => [:get, :post, :options, :delete, :put]
      end
    end
  end
end
unDeveloper commented 8 years ago

Try this in your application_controller.rb

  before_filter :add_allow_credentials_headers
  skip_before_filter :verify_authenticity_token
  before_filter :cors_preflight_check
  after_filter :cors_set_access_control_headers

  before_filter :authenticate_user!, except: [:new, :create]

  def cors_set_access_control_headers
    headers['Access-Control-Allow-Origin'] = '*'
    headers['Access-Control-Allow-Methods'] = 'POST, GET, PUT, DELETE, OPTIONS'
    headers['Access-Control-Allow-Headers'] = 'Origin, Content-Type, Accept, Authorization, Token'
    headers['Access-Control-Max-Age'] = '1728000'
  end

  def cors_preflight_check
    if request.method == 'OPTIONS'
      headers['Access-Control-Allow-Origin'] = '*'
      headers['Access-Control-Allow-Methods'] = 'POST, GET, PUT, DELETE, OPTIONS'
      headers['Access-Control-Allow-Headers'] = 'X-Requested-With, X-Prototype-Version, Token'
      headers['Access-Control-Max-Age'] = '1728000'

      render :text => '', :content_type => 'text/plain'
    end
  end

  def add_allow_credentials_headers
    # https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS#section_5
    #
    # Because we want our front-end to send cookies to allow the API to be authenticated
    # (using 'withCredentials' in the XMLHttpRequest), we need to add some headers so
    # the browser will not reject the response
    response.headers['Access-Control-Allow-Origin'] = request.headers['Origin'] || '*'
    response.headers['Access-Control-Allow-Credentials'] = 'true'
  end
lucasloliveira commented 8 years ago

This seems to extend the span between login into the application and getting the 401, but doesn't seem to fix the problem yet 😢

Started POST "/api/v1/tips/byaddress" for ::1 at 2016-09-04 23:24:23 -0700
Processing by TipsController#list_by_address as HTML
  User Load (0.5ms)  SELECT  "users".* FROM "users" WHERE "users"."uid" = $1 LIMIT 1  [["uid", <user_id>]]
Filter chain halted as :authenticate_user! rendered or redirected
Completed 401 Unauthorized in 62ms (Views: 0.2ms | ActiveRecord: 0.5ms)
unDeveloper commented 8 years ago

Looking at the query, seems to get the wrong param, or you changed it when you post it? Seems to be received an user_id instead of the user id.

SELECT  "users".* FROM "users" WHERE "users"."uid" = $1 LIMIT 1  [["uid", <user_id>]]
lucasloliveira commented 8 years ago

My bad.

Adding "before_filter :authenticate_user!, except: [:new, :create]" to the application_controller.rb wouldn't let me login, returning me:

{"errors":["Authorized users only."]}

So I moved it to the only controller that I actually need it for now, but didn't add the "except: [:new, :create]" part, because I'm not using those methods there and didn't think it would make a difference. Adding that seems to have fixed the issue!

Thank you for your help @unDeveloper !! I really appreciate it 😃

copremesis commented 7 years ago

keep it up or check out my repo here devise token auth

ghost commented 4 years ago

The problem is before_action :authenticate_user! in ApplicationController.

Why? See the devise_token_auth's inheritance structure.

DeviseTokenAuth::SessionsControlle < DeviseTokenAuth::ApplicationController
DeviseTokenAuth::ApplicationController < DeviseController
DeviseController < ApplicationController

Because of the inheritance structure, all actions like sessions#create in devise_token_auth call authenticate_user!. When we request sign_in, our rails app check access-token that actually not exist s in header and it returns Filter chain halted as :authenticate_user! rendered or redirected. The before action should not be called before sessions#create

Solutions

  1. Use other class name ( not ApplicationController )
  2. Use before :authenticate_user! in only controller that you actually need
  3. Add config.parent_controller = "ActionController::Base" in config/devise.rb