For my Users Controller, I'd like to always load in the user as the #user instance variable. This is done by loading in the user from an external authorization link with a callback.
Here is a minimal example:
class UsersController < ApplicationController
before_action :authorize_user
skip_before_action(:authorize_user, { :only => [:auth_callback] })
# this is my before action meant to load in the #user instance variable
def authorize_user
# if user is not stored in session, get authorization from external link
if !session.key?(:user_hash)
redirect_to(external_auth_link)
# otherwise, loads user from hash
else
user_hash = session[:user_hash]
#user = get_user_from_hash(user_hash)
end
end
# this is the callback from the external authorization link
def auth_callback
user = get_user_from_callback()
session[:user_hash] = user.to_hash
# the line in question
# should I redirect here or what?
end
def page1
# some page using the #user instance variable
render({:template => "users/page1.html.erb"})
end
def page2
# another page using the #user instance variable
render({:template => "users/page2.html.erb"})
end
end
When calling page1 or page2, I'd like the before_action to properly load in the #user instance variable, then return to the appropriate method.
Currently, the before_action works fine when user_hash is already stored in session.
However, when user_hash is not saved in session, it redirects the the external authorization, then goes to the callback, then doesn't know where to go after. It wants me to redirect or render something. How could I get it to redirect to the action (page1 or page2) that was originally called?
Ex: If I call page1 when user_hash is not stored in session it will go to:
authorize_user
The external authorization
auth_callback
page1
I've tried to remove the code unrelated to this issue, but let me know if anything else would be helpful. Thanks for any suggestions.
If possible, try getting an additional parameter from the service that handles the auth and redirects to auth_callback. That additional parameter can tell you where to redirect.
If not possible, then you can save the current action to session in authorize_user.
def authorize_user
# if user is not stored in session, get authorization from external link
if !session.key?(:user_hash)
# save action to session
session[:redirect_to] = action_name
redirect_to(external_auth_link)
# otherwise, loads user from hash
else
user_hash = session[:user_hash]
#user = get_user_from_hash(user_hash)
end
end
Then use it to redirect in callback.
See this topic on getting action name in rails.
Alternatively, you can save the full route or path to the session as well.
Please bear in mind that storing this in session is not the best choice, try getting an additional parameter from auth handler if you can.
Related
If I access http://example.com, I am first taken to the login page and after successful login, to the home page.
If I access http://example.com/myusers, after successful login, I want to be redirected to the current page and not the home page.
After reading a few online posts, I understood that in my application_controller.rb I can manipulate the after_sign_in_path_for method. I tested this by adding a specific url to this method and I always get redirected to that url after a successful login.
def after_sign_in_path_for(resource)
"http://example.com/users"
end
How can I make this method capture the URL that the user requested before signing in and redirect the user accordingly?
Devise comes with something like this out-of-the-box. As part of the failure scenario it stores the last request. This "How-to" should help you with the solution. Note that you need to call this method in your before_filter of the controller(s):
before_filter: authenticate_user!
https://github.com/plataformatec/devise/wiki/How-To:-Redirect-back-to-current-page-after-sign-in,-sign-out,-sign-up,-update#a-simpler-solution
You can store the requested URL in a session variable.
session[:return_to] = request.original_url
You can set the session variable inside of an before_action :authorize method in your application controller, this may make sense somewhere else depending on your authorization strategy.
def authorize
if current_user_authorized?
# Authorized...
else
# Record the requested page URL and redirect to the login page.
session[:return_to] = request.original_url
redirect_to login_url, error: "Not authorized."
end
end
To use it you can write:
def after_sign_in_path_for(resource)
session[:return_to] || root_url
end
I am using devise and want to redirect users to a confirmation page upon signup, this is what I am doing right now:
users/registrations_controller.html.erb
class Users::RegistrationsController < Devise::RegistrationsController
def confirm_email
end
private
def after_inactive_sign_up_path_for(resource)
users_confirmyouremail_path
end
end
config/routes.rb
devise_scope :user do
get 'users/confirmyouremail' => 'users/registrations#confirm_email'
end
I have no problem with redirecting the page after signup. However, I think it is quite weird that anyone can visit the page with url like `host.com/confirmyouremail' and see the confirmation page. Are there any ways I can write a route that will use random code that is allow only for one time visit? Thanks in advance.
Maybe something like this:
before_action :authenticate_user!
def confirm_mail
redirect_to root_path if current_user.confirmed
...
end
You are storing in the database if the user has already confirmed his account. If his account is confirmed then he won't be able to access this page. You can redirect to whatever page you want. A user without any account won't be able to access this page because of the before action
In case the user is not logged in when he accesses this confirm_mail page you have different possibilities. You could use a session or a cookie:
# after sign up:
session[:confirm] = true
# alternatively a cookie
cookies[:confirm] = true
Then in the confirm mail action:
def confirm_mail
if session[:confirm].blank? # or cookies[:confirm].blank?
redirect_to root_path
end
# otherwise delete the field from the session
session.delete(:confirm)
# alternatively the cookie
cookies.delete(:confirm)
end
Another way would be by using a Token. You create a new model like ConfirmMailToken. Then on sign up you create a new token and redirect the user to the confirm page with the token as a URL param. Then in the confirm_mail action you check if a token is available and delete it if it is. This way you ensure that the page is only shown after redirect.
I have a blog project in Ruby on Rails.
I want to allow user to leave message without sign in BUT after creating the message I want him to sign in or signup.
The idea is like following, user clicks on "Create Message" then he fill the form of the message, then he clicks on the "Save Message" and if user is not signed in then he redirected to signin/signup form, after signin the system should identify that we have a message for this user, save the message with correct user.id.
I know it's pretty standard technique, if you know some good pattern to do so I would be glad.
In your create action, you should distinguish between 1) user is known and 2) user is not known yet. In the second case, you should store the id of the created message in your session, then redirect to the login/signup page:
session[:message] = #message.id
redirect_to new_user_session_path
Next step is to interfere with both the create action of the registrations controller (user signed up after your redirect), and with the create action of the sessions controller (user signed in after your redirect)
In both controllers, you can add something like
after_filter :assign_pending_message, only: :create
with
def assign_pending_information_request
# User succesfully created?
return unless #user.id
if session[:message]
if Message.exists?(id: session[:message])
message = Message.find(session[:message])
message.user = #user
message.save!
end
session.delete(:message)
end
end
If you need more details, just let me know!
Devise doesn't allow POST parameters to be sent on authentication, so you need to send message parameters via GET, So here user will be redirected to the login page on clicking save message because of the applied filter, Here is a demo
class MessagesController < ApplicationController
before_filter :authenticate_user!, :except => [:new]
def new
#message = Message.new
end
def get_message
#message = Message.new(params[:message])
#message.save
end
end
In the form you explicitly need to mention the URL of get_message action otherwise it will take create by default, create is a post method so it is of no use to us to implement this.
in your routes.rb
resources :messages do
get :get_message, :on => :collection
end
I have a Devise customisation issue that I'm struggling with.
We have a query parameter included in some email links that need to be passed to the login URL if the user is not already authenticated when they click on the link. For example, if the email link is:
http://my.host.com/my/path/1234?x=y&foo=bar
I'd like unauthenticated users to be redirected to
http://my.host.com/login/?foo=bar
i.e., one specific query param needs to be passed – I don't care about any others on the login form, but if all the query params have to be passed I could live with that.
I've scoured SO, and the docs & source for both Devise and Warden, but can't find a straightforward way of doing this. Apologies if I'm missing something obvious.
In the end, we got the effect we wanted by going down a very slightly different route.
Rather than including the param in the query URL of the redirect to the login, we were able to modify the code concerned to accept a session parameter instead.
Then, in application_controller.rb:
class ApplicationController < ActionController::Base
before_filter :persist_login_param
...
def persist_login_param
session[:foo] = params[:foo]
end
end
And in the sessions controller, we act upon the value of the session parameter, discarding it once done.
Slightly messy, but it works and gets code pushed out to production now, which is the important thing!
Can use unless statement in before_action, then unauthentication users can see any devise controller actions
class ApplicationController < ActionController::Base
...
before_filter :sending_params
before_action :check_auth, unless: :devise_controller?
...
protected
def check_auth
unless user_signed_in?
redirect_to new_user_session_path(sending_params)
end
end
def sending_params
params.permit(:first_param, :second_param)
end
...
And can access to params without generation devise controllers by generate devise views and use in views
<%= params[:first_param] %>
If need send params to omniauth controller, you can change link in devise views
<%= link_to 'Sign in by provider', omniauth_authorize_path(:user, :provider, first_param: params['first_param']) %>
Where :user - name devise model, :provider - name omniauth provider (:facebook i.e.)
In omniauth controller you can use
def first_param
request.env['omniauth.params']['first_param'] ||= ''
end
For an unauthenticated user, in order to pass specific params foo=bar out of http://my.host.com/my/path/1234?x=y&foo=bar to your login, perform as below:
Assuming the above email link goes to action myaction then add the following code to myaction
def myaction
if member_signed_in?
redirect_to root_path
else
## If user is unauthenticated direct them to login page with specific params
redirect_to new_member_session_path(foo: params[:foo])
end
end
Above action will pass params foo to the devise login page and the actual link in browser address bar would look as below:
http://my.host.com/login?foo=bar ## Without / after login
You can access the param by overridding Devise::SessionsController. For Example :
class Users::SessionsController < Devise::SessionsController
...
def new
#foo = params[:foo]
super
end
...
end
I'm using Devise in a Rails application I'm writing, and I want to let users go back to where they were after signing in or signing up.
For example, if I have a "comments" Controller that is protected by:
before_filter :authenticate_user!
Then I want users who click a "Comment Now!" button (and are therefore redirected to the new action in CommentsController) to log in and then have Devise redirect them to the new action (or wherever they were) in CommentsController, not to the generic root of the application, or to a generic after_sign_in_path.
Looking through the RDOC for Devise, I found this method that makes it look as if Devise has at least the capability to do something like this on its own, but I can't figure out a way.
OK, so I've done some more experimentation, and working with Kormie's info, I've got a working solution.
From what I can determine, before_filter authenticate_user! does not save the route for returning the user. What I did was this:
First, I added an extra before_filter at the top of my controller
before_filter :store_location
before_filter :authenticate_user!
Then, I wrote the store_location method at the bottom of the controller
private
def store_location
session[:user_return_to] = any_old_route_path
end
I don't claim this is perfect, but it works for me. (The downside for anyone else wanting to use it, is that it only supports one return path per controller. This is all I need for myself, but it is only a slight improvement over the one return path per app that I was using previously.) I would really appreciate anyone else's insights and suggestions.
Devise should do this by itself. The authenticate_user! filter also did not want to work for me when the route to the action was set via PUT method. When I have changed this to GET in routes.rb devise started to work as expected.
The simple way to do this:
# Modified from https://github.com/plataformatec/devise/wiki/How-To:-redirect-to-a-specific-page-on-successful-sign-in
class ApplicationController < ActionController::Base
def after_sign_in_path_for(resource)
stored_location_for(resource) || your_defaut_path
end
end
I think by default Devise saves the route but you may be usinging
sign_in #user
this should redirect you
sign_in_and_redirect(#user) #assuming you are sigining in that resource
Have you tried after_sign_in_path_for? If you define that method in your ApplicationController it should override the default implementation on a per controller basis.
Adapted from Devise Wiki how to:
Redirect back to current page after sign in, sign out, sign up, update
Redirecting back to the "current page" involves saving the current url in the session and then retrieving the url from the session after the user is authenticated / signed out. This should only really be done for GET requests as the other http methods (POST, PUT, PATCH, DELETE) are not idempotent and should not be repeated automatically.
To store the location for your whole application use before_action to set a callback (Use before_filter in Rails versions before 4.0).
This example assumes that you have setup devise to authenticate a class named User.
# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
before_action :store_user_location!, if: :storable_location?
# The callback which stores the current location must be added
# before you authenticate the user as `authenticate_user!` (or
# whatever your resource is) will halt the filter chain
# and redirect before the location can be stored.
before_action :authenticate_user!
# To redirect to the stored location after the user signs
# signs in you would override the after_sign_in_path_for method:
def after_sign_in_path_for(resource_or_scope)
# *My note, not wiki*: you may need a fall back as
# stored_location_for can return nil. I've added root_path
stored_location_for(resource_or_scope) || root_path
end
private
# Its important that the location is NOT stored if:
# - The request method is not GET (non idempotent)
# - The request is handled by a Devise controller
# such as Devise::SessionsController as that could
# cause an infinite redirect loop.
# - The request is an Ajax request as this can lead
# to very unexpected behaviour.
def storable_location?
request.get? && is_navigational_format? &&
!devise_controller? && !request.xhr?
end
def store_user_location!
# :user is the scope we are authenticating
store_location_for(:user, request.fullpath)
end
end
Reference
Devise How To: Redirect back to current page after sign in, sign out, sign up, update