Add params to after_sign_in_path_for - ruby-on-rails

I'd like to add parameters in the path returned in my after_sign_in_path_for function.
When a user is not authenticated and submits a form, i store the params and my website redirects him to the sign in form.
def create
if current_user.nil?
session[:form_data] = params
redirect_to new_user_registration_path
else
# here I handle form params
end
end
Then the user logs in and here is my after_sign_in_path_for function.
def after_sign_in_path_for(resource)
if session[:form_data].present?
'/transactions#create'
else
session[:previous_url] || root_path
end
end
If session[:form_data] exists, i would like to redirect him to transactions#create, but with session[:form_data] contents as parameters.
I tried to use redirect_to but it throws an exception since the devise function calling after_sign_in_path_for also calls redirect_to.
Is there any way to do that ?

The question here is if you should - redirects are GET requests. Your create action should only respond to a POST request since a GET request is stored in the browser history and may be inadvertently repeated.
What you could do instead is redirect to transactions#new and use the params to pre-fill the form:
def new
#transaction = Transaction.new(new_transaction_params)
end
def new_transaction_params
params.fetch(:transaction, {})
.permit(:a, :b, :c)
end
def after_sign_in_path_for(resource)
if session[:form_data].present?
new_transaction_path(query: session[:form_data])
else
session[:previous_url] || root_path
end
end
But if you have saved the form data in the session anyways there is no need to pass it via the params. In fact sending parameters in the query string is somewhat less secure.
This assumes that you have setup the routes with:
resources :transactions

Related

sRails 5 Devise after_sign_in_path

I have a Rails 5 app with Devise. Each user has a role_id where they are assigned a role upon creation. I'm trying to use the after_sign_in_path_for method that Devise gives to redirect to a specific page on login based on the role.
Below is what I have so far, but it doesn't work when trying to sign out a disabled user.
class ApplicationController < ActionController::Base
def after_sign_in_path_for(resource)
case resource.role_id
when Role.find_by(name: "admin").id
root_path
when Role.find_by(name: "disabled").id
destroy_user_session_path
else
super
end
end
end
I'm able to sign in when I'm an admin user and it redirects. But if I try to sign in as a user whose role is disabled, it tries to tear down the session then raises an exception of No route matches [GET] "/users/sign_out". I know the method destroy_user_session_path expects a delete method but how can I pass this in the application controller?
What am I doing wrong here?
Update
I tried the sign_out(resource) as suggested in the first answer, and it raises an exception undefined methodto_model' for true:TrueClassin mymy_sessions_controller.rb` which I use to override the create method to set a login token and limit concurrent sessions. Here is the controller.
class MySessionsController < Devise::SessionsController
skip_before_action :check_concurrent_session
def create
super
set_login_token
end
private
def set_login_token
token = Devise.friendly_token
session[:token] = token
current_user.login_token = token
current_user.save(validate: false)
end
end
You can check roles inside MySessionsController#create and prevent logging if the role not valid instead of allowing user to login then logout
def create
unless current_user.role_id == Role.find_by(name: "disabled").id
super set_login_token
else
redirect_to new_user_session_path, alert: "You can't log in"
end
end
You can also use active_for_authentication? and inactive_message methods in user model to prevent him from login. in /app/models/user.rb:
def active_for_authentication?
super and self.role_id != Role.find_by(name: "disabled").id
end
def inactive_message
"You can't log in"
end
destroy_user_session_path is making [GET] "/users/sign_out" request.
You can use sign_out or reset_session function to delete session directly.
Hope this answer works for you.
Use devise's sign_out(resource) method instead of destroy_user_session_path. This method will destroy the user session.

Persisting form data after login/sign UP through devise

I am working on a rails app which uses devise for authentication. In my app there is a query form, but only a signed in user can fill the query form.
If the user is not signed in, he can fill the data in the from, but as he clicks submit I want him to be redirected to the sign_up page, there he can sign_up or sign_in. Once he does that I want him to be redirected back to the previous page and his form data be restored so that he doesn't need to fill the form again.
So far I am able to implement the redirecting back to previous page part but I am unable to persist the form data
Code of my application controller:
class ApplicationController < ActionController::Base
# Prevent CSRF attacks by raising an exception.
# For APIs, you may want to use :null_session instead.
protect_from_forgery with: :exception
#after_filter :store_location
after_filter :store_location
def store_location
# store last url - this is needed for post-login redirect to whatever the user last visited.
return unless request.get?
if (request.path != "/users/sign_in" &&
request.path != "/users/sign_up" &&
request.path != "/users/password/new" &&
request.path != "/users/password/edit" &&
request.path != "/users/confirmation" &&
request.path != "/users/sign_out" &&
!request.xhr?) # don't store ajax calls
session[:previous_url] = request.fullpath
end
end
def after_sign_in_path_for(resource)
session[:previous_url] || root_path
end
def after_sign_out_path_for(resource_or_scope)
request.referrer
end
end
Can anyone please help me in persisting the form data so that he doesn't need to fill the form again.
In the create action of your form controller, you must check if the user is logged in or not.
Basically, what you want is, if the user is not connected, store the params received into a session variable and redirect the user to the registration form and use the Devise built in function after_sign_in_path_for to redirect him to your form if Rails detects a session variable.
It should look something like that:
# forms_controller.rb
class FormsController < ApplicationController
# Make sure not to filter 'create' as we'll be handling that with our redirect
before_filter :authenticate_user!, :except => [:create]
...
def create
# Check to see if the user is registered/logged in
if current_user.nil?
# Store the form data in the session so we can retrieve it after login
session[:form_data] = params
# Redirect the user to register/login
redirect_to new_user_registration_path
else
# If the user is already logged in, proceed as normal
...
end
end
# application_controller.rb
def after_sign_in_path_for(resource)
# redirect to the form if there is a form_data in the session
if session[:form_data].present?
#redirect to your form path
else
#if there is not temp list in the session proceed as normal
super
end
end
To repopulate your form, in your controller action 'new', you could do something like:
if session[:form_data]
#my_form = Form.new(session[:form_data])
session[:form_data] = nil
#my_form.valid? # run validations to populate the errors[]
else
#my_form = Form.new
end
For more information about it, take a look at this tutorial.

How to us a permanent cookie in a before filter to filter for first time guests

I'm setting up a simple survey on my web page.
I want to add a before_filter so that the same person can't take the survey more than once.
My idea is to
1) create and save a remember_token to each survey when it is submitted.
2) create a cookie based on that remember token to be placed on the submitter's browser
3) Every time some visits the page, use a before filter to make sure they don't have a cookie that matches a survey in the database.
I put together the below, but for some reason, it automatically redirects to the thanks_path, regardless of whether I have a remember token?
Why does it do this? Am I using the session cookie incorrectly?
My surveys_controller is as below
before_filter :new_visitor, only: [:new, :create]
def new
#this is the survey form
#survey = Survey.new
end
def create
#this submits the survey and creates a cookie on the client's browser
#survey = Survey.new(params[:survey])
if #survey.save
cookies.permanent[:remember_token] = #survey.remember_token
redirect_to thanks_path
else
render action: "new"
end
end
def thanks
#blank page that just says, "thanks for taking the survey!"
end
def new_visitor
# if a browser has a survey cookie, redirect to thanks page
unless Survey.find_by_remember_token(cookies[:remember_token]).nil?
redirect_to thanks_path
end
end
I am creating the remember token in my Survey model.
class Survey < ActiveRecord::Base
before_save :create_remember_token
def create_remember_token
self.remember_token = SecureRandom.urlsafe_base64
end
end
I think you need to test for the existence of the cookie[:remember_token] before using it as an argument to find_by_remember_token(). Only if cookies[:remember_token] is not nil and a record is found do you redirect to the thanks_page.
if cookies[:remember_token] && Survey.find_by_remember_token(cookies[:remember_token])
redirect_to thanks_page
end
unless Survey.find_by_remember_token(cookies[:remember_token]).nil?
this means if Survey not nil then redirect, i think you need to change to
unless Survey.find_by_remember_token(cookies[:remember_token])
or
if Survey.find_by_remember_token(cookies[:remember_token]).nil?

Rails Devise - Pass URL to login

Is there a way for me to pass an URL to the Devise login page, so that when a user logs in, he/she is redirected back to that URL?
Something like:
/login?passthru=/somethingawesome
Or is it better to set a session variable?
Have a method to store the redirect location and a method to access the stored redirect location in application_controller:
def store_location(path)
session[:return_to] = request.request_uri || path
end
def redirect_back_or_default(default)
redirect_to(session[:return_to] || default)
session[:return_to] = nil
end
Override the after_sign_in_path_for method to redirect the user to the desired location:
def after_sign_in_path_for(resource_or_scope)
redirect_back_or_default(resource_or_scope)
end
Devise Wiki: How To: Redirect to a specific page on successful sign in out
By the way, the above method is untested and you should test it.
Here's what I did
1) In your template set up your sign_in login
as follows: Im passing request.fullpath here as an example you can replace this with whatever you want.
<%= link_to "Log in", new_user_session_path(:passthru => request.fullpath %>
2) Next modify ApplicationController as follows: we add a before_filter which sets passthru in the session if it exists. Then we override after_sign_in_path_for to look in the session for passthru. If its not there it will default to root_path. As long as you handle logins with params consistently everywhere this should work. Although it might need some tweaking.
before_filter :store_location
def store_location
session[:passthru] = params[:passthru] if params[:passthru]
end
def redirect_back_or_default(default)
session[:passthru] || root_path
end
def after_sign_in_path_for(resource_or_scope)
redirect_back_or_default(resource_or_scope)
end
Update for Newer Versions (Ruby 2.2.0, Rails 4.2.0, Devise 3.2.4):
Application_Controller.rb
before_action :store_location
private
def store_location
session[:requestUri] = params[:requestUri] if params[:requestUri].present?
end
Devise Sessions_Controller.rb
# Custom After Sign in Path
def after_sign_in_path_for(resource_name)
if session[:requestUri]
session.delete(:requestUri)
else
super
end
end
View xxxx.html.erb
<%= link_to ('Get This'), some_require_auth_path(#something.id, requestUri: request.fullpath), class: 'button' %>

Rails: Warden/Devise - How to capture the url before login/failed access

I am trying to figure out how to redirect a user to the page they logged in from (or failed to login) using Warden/Devise. I figure there is a session variable somewhere that is either available or could be made available.
E.g.
Scenario 1:
Non-authorized user goes to protected page X;
Redirected to login page;
User logs in;
User redirected to protected page x
Scenario 2:
Non-authorized user wants to take a protected action on page x;
User clicks on login link;
User logs in;
User redirected to page x where the action is now available
Any pointers are appreciated.
Thanks!
There is a devise helper method called after_sign_in_path_for(resource) (http://rdoc.info/github/plataformatec/devise/master/Devise/Controllers/Helpers) , and a session variable called session[:"user.return_to"] which stores the last url. The after_sign_in_path_for method needs to return a string, then devise automatically uses this path to redirect the user after login.
In my application controller I have put the following which redirects my users to the home page if the session variable is not set:
def after_sign_in_path_for(resource)
(session[:"user.return_to"].nil?) ? "/" : session[:"user.return_to"].to_s
end
If your using CanCan for authorization you can accomplish this be adding the following. If not, you should be able to adapt the concepts into your current authorization system.
app/controllers/application_controller.rb
rescue_from CanCan::AccessDenied do |exception|
flash[:error] = exception.message
if user_signed_in?
redirect_to root_url
else
# Adds the protected page to the login url but only if the user is not logged in
redirect_to login_path(:next => request.path)
end
end
def after_sign_in_path_for(resource_or_scope)
# if a protected page found, then override the devise after login path
params[:user]["next"] || super
end
app/views/devise/sessions/new.html.erb
<% if params[:next] %>
<%= f.hidden_field :next, :value => params[:next] %>
<% end %>
Instead of using session variables this solution uses params in the URL to keep track of the protected page.
Wow, just realized that devise (3.5.2) does this all by itself behind the scenes (around Devise::SessionsController#new action), no additional controller modifications required.
If you need to explicitly store/get previous location, please see my previous answer:
Currently (Fall 2015) there's a sexier way to do that:
Devise::Controllers::StoreLocation#store_location_for:
# Stores the provided location to redirect the user after signing in.
# Useful in combination with the `stored_location_for` helper.
store_location_for :user, dashboard_path
redirect_to user_omniauth_authorize_path :facebook
Devise::Controllers::StoreLocation#stored_location_for:
# Returns and delete (if it's navigational format) the url stored in the session for
# the given scope. Useful for giving redirect backs after sign up:
redirect_to stored_location_for(:user) || root_path
The methods handle related session key and value deletion after reading, all you need is to provide your :resource key (:user in the example above) and a path to store (dashboard_path in the example above). See source for the details.
As for the actual answer it'll be something like that:
class ApplicationController < ActionController::Base
rescue_from CanCan::AccessDenied, with: :access_denied
# ...
private
def access_denied(exception)
store_location_for :user, request.path
redirect_to user_signed_in? ? root_path : new_user_session_path, alert: exception.message
end
def after_sign_in_path_for(resource)
stored_location_for(:user) || root_path
end
end
You can use request.referer to get the previous URL.
here's the best I could come up with. Works perfectly also with facebook authentication. by adding more restrictions to the prepending of urls to the session variable you can remove more and more paths you don't want the user to return too (e.g. callbacks, splash pages, landing pages, etc)
#ApplicationsController
after_filter :store_location
def store_location
session[:previous_urls] ||= []
# store unique urls only
session[:previous_urls].prepend request.fullpath if session[:previous_urls].first != request.fullpath && request.fullpath != "/user" && request.fullpath != "/user/login" && request.fullpath != "/" && request.fullpath != "/user/logout" && request.fullpath != "/user/join" && request.fullpath != "/user/auth/facebook/callback"
# For Rails < 3.2
# session[:previous_urls].unshift request.fullpath if session[:previous_urls].first != request.fullpath
session[:previous_urls].pop if session[:previous_urls].count > 3
end
def after_sign_in_path_for(resource)
#url = session[:previous_urls].reverse.first
if #url != nil
"http://www.google.com" + #url
else
root_path
end
end

Resources