Search code examples
ruby-on-railsauthenticationdeviseruby-on-rails-4warden

Adding a validation field for sign in using Devise and Rails 4.0


I'm trying to add another field in the sign in page using Devise and Rails 4.0. A user needs to provide their username, password and an organization/location code. A user has_and belongs_to_many locations, and when logged in, this organization code should be saved in sessions.

At this point, I think I got most of it working (please let me know if there is a better way of doing this), but I don't know how to handle what happens if an invalid location code is entered. Here's what I have so far.

class SessionsController < Devise::SessionsController
  def create
    user = User.find_by_username(params["user"]["username"])
    org_code = params["user"]["check_org_code"].downcase
    user.locations.each do |location|
      if location.org_code == org_code
        super
        session[:location] = location
      else
        < return a warden.authenticate! fail? >
        super
      end
    end
  end
end

Solution

  • I initially solved this by using throw(:warden) and while this works, is probably not the best solution. Later I came across a custom devise strategy example by r00k and implemented my own variation. I've included both solutions below.

    r00k-esque solution N.B. there were some bits of code is r00k's solution that didn't work for me. In case r00k's solution isn't working for you, please note the differences with "fail!" and "valid_password?".

    # config/initializers/local_override.rb
    
    require 'devise/strategies/authenticatable'
    
    module Devise
      module Strategies
        class LocalOverride < Authenticatable
          def valid?
            true
          end
    
          def authenticate!
            if params[:user]
              user = User.find_by_username(params["user"]["username"])
              if user.present?
                code = params["user"]["check_org_code"].downcase
                codes = user.locations.pluck(:org_code).map{ |code| code.downcase }
                check_code = code.in?(codes)
                check_password = user.valid_password?(params["user"]["password"])
    
                if check_code && check_password
                  success!(user)
                else
                  fail!(:invalid)
                end
              else
                fail!(:invalid)
              end
            else
              fail!(:invalid)
            end
          end
        end
      end
    end
    
    Warden::Strategies.add(:local_override, Devise::Strategies::LocalOverride)
    
    # config/initializers/devise.rb
    config.warden do |manager|
      manager.default_strategies(:scope => :user).unshift :local_override
    end
    

    Initial solution

    class SessionsController < Devise::SessionsController
      def create
        user = User.find_by_username(params["user"]["username"])
        org_code = params["user"]["check_org_code"].downcase
        matching = false
    
        user.locations.each do |location|
          if location.org_code == org_code
            super
            session[:location] = location
            matching = true
            break
          end
        end
    
        if matching == false
          throw(:warden, :message => :invalid)
        end
      end
    end