I have certain actions that require a user to be authenticated. If the user attempts these actions, I want my website to automatically be able to redirect them to log in and then automatically "replay" whatever the original action was. I am using Devise for authentication and CanCan for role/abilities. To this end, I have the following code:
class ApplicationController < ActionController::Base
# CanCan detected an role violation. Redirect to login page.
rescue_from CanCan::AccessDenied do |exception|
session[:saved_params] = params
user_signed_in? ? render_forbidden : render_unauthorized
end
end
class Users::SessionsController < Devise::SessionsController
def after_sign_in_path_for(resource)
if session[:saved_params]
controller = session[:saved_params][:controller].capitalize+"Controller"
replay_with_params(controller, session[:saved_params][:action], session[:saved_params])
end
end
end
Desired Behavior
The user flow would be like this:
- User arrives at
Event#newform, fills out form, and submits it. - In
ApplicationController, CanCan detects anonymous user attempting to do something that they are not allowed to do. App Controller saves the params of the current request and redirects to login page. - User logs in at Devise login page. The
SessionControllerdetectssession[:saved_params]and attempts to "replay" the original request from Step 1.
Attempt #1: Directly invoking the controller's action
class ApplicationController < ActionController::Base
# Replay's another controller's action
def replay_with_params(controller,action,params)
if controller.instance_of? String
klass = Object.const_get(controller)
c = klass.new
elsif controller.instance_of? Class
c = controller.new
else
throw "Unrecognized controller type: #{controller}"
end
c.params = params
c.dispatch(action, request)
c.response.body
end
end
The replay_with_params function is something I tweaked from https://stackoverflow.com/a/7085969/86421.
The Problem
The problem with this is that I cannot reuse the authenticity token from another request. I either need to generate a new one, or skip_before_action :verify_authenticity_token on the target controller. I dont think generating a new token makes sense since there is no front-end to the replayed request. Skipping verification of the token is not appealing for obvious reasons.
Attempt #2: Redirection
Redirection would actually make more sense, I think, but it would only be feasible for GET requests. In my scenario, I need to be able to work with all verbs.
Fundamentally Different Solutions
I could intercept with a modal popup and make the user login there while keeping the original html form intact. This may be the best solution, but I prefer the flow of my original solution better. One of the reasons why I dont like this is because now I have to build my CanCan role/ability logic into my views, or else manually add the modal code into each view. The former seems like a violation of MVC and the latter is tedious and error prone.
Is what I am trying to do possible? I am running into roadblocks on every angle. How can I intercept a request, force someone to login and then replay the original request?