codedecoder

breaking into the unknown…

Devise session out handling for ajax request rails

Leave a comment

Devise work out of box for maintaining user session. But it fails to handle redirect in case of ajax request.

Problem  Description:

  1. User logged in to Rails Application using Devise Auth flow.
  2. Go to fill a form but left for some work and comes back in 15 minutes(divise timeout configured for 15 minutes).
  3. Try to submit the form through ajax call and you show the loder to indicate form got submitted.
  4. The loader keep rotating as the request intercepted in between as unauthorized(401 error in console).

Here Devise did its job and unauthorized the user. The only thing it failed to do is redirect user to login page. If you reload the page you can see that user redirected to login page. So our goal is to exhibit same behavior in ajax request also.

Solution:

The only thing we have to handle is that, make Devise to redirect to login page if user session got expired when we make Ajax call. I looked for the way Devise handling the redirect and got idea from this issue thread on devise. The redirect logic of devise in case of unauthentication is written in the file failure_app.rb .

The redirect logic is written as below in the file

def redirect_url
  if warden_message == :timeout
    flash[:timedout] = true if is_flashing_format?

    path = if request.get?
      attempted_path
    else
      request.referrer
    end

    path || scope_url
  else
    scope_url
  end
end

If you see it do not handle redirect for ajax request i,e of format .js.

The beauty of Ruby is that we can open any class and add our custom behavior. So lets customize the above class. Create a file lib/custom_failure_app.rb and add below line to it.

class CustomFailureApp < Devise::FailureApp 
  def redirect_url 
    if request.xhr? 
      send(:"new_#{scope}_session_path", :format => :js)
    else
      super
    end
  end
end

So we just told Devise that, if xhr request, redirect to login page with format js.

Now we have to configure Devise to use our CustomFailure APP – config/initializer/devise.rb

config.http_authenticatable = false
config.http_authenticatable_on_xhr = false
config.navigational_formats = ["*/*", :html, :js]

config.warden do |manager|
  manager.failure_app = CustomFailureApp
end

So next time whenever ajax request is made on session timeout user will be redirected to users/sign_in.js path.

create view/devise/sessions/new.js.erb file and add below line:

 window.location = "/"

At this point I was expecting that, things will start working now. But it is close but not working as expected. Below is the log:

Completed 401 Unauthorized in 31ms (ActiveRecord: 18.2ms)

Started GET “/users/sign_in.js” for 127.0.0.1 at 2018-07-02 21:26:22 +0530
Processing by Customized::SessionsController#new as JS
Completed 406 Not Acceptable in 3ms (ActiveRecord: 0.0ms)

ActionController::UnknownFormat (ActionController::UnknownFormat):

So, the desired behavior is there, on ajax session expire user got redirected to users/sign_in.js . The error here is because new action of Devise session controller is defined as below:

  # GET /resource/sign_in
  def new
    self.resource = resource_class.new(sign_in_params)
    clean_up_passwords(resource)
    yield resource if block_given?
    respond_with(resource, serialize_options(resource))
  end

It is using respond_with which is not handling .js format. So I overrided it as below:

1 – Changed default devise routes

  devise_for :users, controllers: { sessions: 'customized/sessions' }

2 – Overrided the new method in controllers/customized/sessions.rb .

class Customized::SessionsController < Devise::SessionsController
    
  # GET /resource/sign_in
  def new
    self.resource = resource_class.new(sign_in_params)
    clean_up_passwords(resource)
    yield resource if block_given?
    respond_to do |format|
      format.js
      format.html
    end
  end
end

Now everything working fine. If session got expired while ajax request and user got redirected to login page.

Author: arunyadav4u

over 10 years experience in web development with Ruby on Rails.Involved in all stage of development lifecycle : requirement gathering, planing, coding, deployment & Knowledge transfer. I can adept to any situation, mixup very easily with people & can be a great friend.

Leave a comment