Devise - override redirect for the custom login page - ruby-on-rails

I have the following interesting problem. I've created a secondary login form. From that secondary form I want the user always to be redirected to the specific form. I believe Devise is handling redirects in the following function in ApplicationController:
def after_sign_in_path_for(resource)
# custom redirect stuff
end
What would be the best way to tell devise "if I am coming from the custom redirect path, always take me to some specific page and disregard what ever is in params[:redirect]". I was thinking of either inspecting referrer url string or storing stuff on the session, but I am not sure.
What would be the best way to achieve this behaviour? Any suggestions would be appreciated!

Inspecting Devise code, I found that it is possible by clearing out the session variable where Devise stores the return path:
session[:user_return_to] = nil # or some explicit path
Note: Assuming the authenticated model is user.rb

Related

Is it possible to do Devise sign in, out, etc without any redirection whatsover?

I'm using Devise, but not using the Devise controllers directly because I'm performing all of the actions through a custom built GraphQL API. One issue I have, for example, is that after enabling confirmable, if a user tries to sign in and I call Devise::Controllers::Helpers#sign_in the user gets redirected to /api/v1/users/sign_in, which doesn't exist and it would be wrong even if it exist. Instead, I need the failure to sign in to be returned back to my code (return value, exception, whatever), so that my API can encode that response to the frontend.
How can I do that?
For example, this is my log in function:
def resolve(email:, password:)
user = User.find_for_authentication(email: email)
if user&.valid_password?(password)
context[:sign_in].call(user)
{ current_user: user }
else
{ errors: [{ message: 'Email or password incorrect.' }] }
end
end
context[:sign_in] is set up in the GraphqlController by including Devise::Controllers::Helpers and then simply:
context = {
current_user: current_user,
sign_in: method(:sign_in),
sign_out: method(:sign_out)
}
Note: I am not using GraphqlDevise because I don't want to use Devise Token Auth, I'm using cookies.
I believe passing devise's sign_in/sign_out methods via context is probably a deadend.
The suggestion in the comment to your question from #Int'l Man Of Coding Mystery is good ie you could use: https://github.com/graphql-devise/graphql_devise.
If you're not keen in introducing another dependency and figuring out how to wire everything you can perhaps go with overriding devise's SessionController.
See for some examples here: Rails - How to override devise SessionsController to perform specific tasks when user signs in?
(but also don't hesitate to look at the source code for the matching Devise release: https://github.com/heartcombo/devise/blob/master/app/controllers/devise/sessions_controller.rb)
Depending on your use case you might be even able to do what you need by using some of the config options - e.g. you can perhaps try to override after_sign_in_path etc.

Devise stored_location_for(resource) returns nil

I read the few posts about troubleshooting stored_location_for here, but can't seem to figure it out and not sure how to troubleshoot.
I tried deleting my custom after_sign_in_path_for, but that didn't work either. My location is never getting saved, although as I understand it after each session/page update it should store the location. Do I need to through that in as a filter manually?
def after_sign_in_path_for(resource)
stored_location_for(resource) ||
if resource.is_a?(Account)
add_quote_to_account(resource)
if resource.applications.any?
edit_application_path(resource.applications(true).last)
else
root_path
end
else
super
end
end
May be you are not storing the location where you want to redirect_to after signing in with devise. Devise provides two methods - store_location_for and stored_location_for
https://github.com/plataformatec/devise/blob/master/lib/devise/controllers/store_location.rb
Suppose your user model is named "user" then
A call to store_location_for(:user, my_desired_path) in your controller will store the url "my_desired_path" in session with key "user_return_to". Basically this method will simply do this - session["user_return_to"] = my_desired_path. Probably you are missing this. I have a booking controller and a "login" action which stores the checkout location for booking in booking controller and then displays a login form for users in rendered view -
def login
my_desired_path = url_for(controller: 'bookings', action: 'checkout')
store_location_for(:user, my_desired_path)
end
Now you can use stored_location_for(:user) to retrieve my_desired_path from your session. So to say, a call to stored_location_for(:user) will return "my_desired_path.
Now if you use stored_location_for in your custom after_sign_in_path_for(:user) then it shall return "my_desired_path".
Additional Point -
A call to stored_location_for(:user) returns session[:user_return_to] but also clears this session after returning the value if your redirect format is navigational format. So a second call to stored_location_for(:user) will not return my_desired_path. And sometimes this is how we want our application to behave. On contrary, if your redirect format is not navigational format then session wont be cleared and a second sign-in will again redirect to same "my_desired_path".
Sometimes you want to redirect to different locations in signing-in from different pages in your application. Suppose, you want to redirect to "\X" on page A and to "\Y" on page B. Now follow these steps -
User in on page A - store_location_for(:user, "\X") is saved in session.
Application provides a sign-in form but User does not sign-in and just browse here and there in your application.
User is on page B and perform a sign-in expecting to land on "\Y" but unexpectedly lands to "\X".
So take care of it in your application. This scenario is more likely to occur in applications which uses AJAX for signing-in.

How to set a session variable after sign in with Devise?

I'm trying to find out where to set a session variable once a user has logged in using Devise. I found this post (and others):
Set a session variable in devise on sign in
So I tried something like this:
protected
# when a user logs in
def after_sign_in_path_for(resource_or_scope)
session[:current_account_id] = current_user.accounts.find_by_is_default(true).id # get id of row where it's is_default is set as true
abort(session[:current_account_id])
end
..but no joy. I don't want to do an alternative redirect (as the issue in the link was asking), just set a session variable when the user logs in, so I'm not sure if this is the callback I'm wanting. Also, I may have my find_by_ method wrong but I was hoping I could debug (using abort) once the script gets that far - but it doesn't appear to be as abort doesn't seem to be called. Any help much appreciated. Thanks
Make sure the protected method is located inside of ApplicationController.

rails devise hook into on_login

I'm looking to hook into devise after login / after session create. How would I go about doing this?
Basically I want to set a users location every time they login, and to do that I need an after login hook of sorts.
Devise is built on Warden, so you can use Warden's after_authentication hook.
Put this in an initializer:
Warden::Manager.after_authentication do |user,auth,opts|
# do something with user
end
The remote IP address and other request info is stored in auth.request (i.e. auth.request.remote_ip).
See https://github.com/hassox/warden/wiki/callbacks
Devise updates the value of the user.current_sign_in_at timestamp on successful login. So, you could simply add a before_save filter to your User model. In that filter, check to see if the value of this field has changed, and if it has, set the users location.
BTW - I'm not sure what you mean by "location" - if you mean IP address, Devise already stores that for you.
Here's a page from the devise wiki: How To: Redirect to a specific page on successful sign in.
In summary, the recommendation is to add the following method to the application controller:
app/controllers/application_controller.rb
def after_sign_in_path_for(resource)
custom_location_for(resource) || welcome_path
end
In the above code, resource means the object (user, account, etc) that you've implemented devise authentication for. (The object that has the devise_for in your routes.)

rails 3 / heroku / devise: how specify different 'landing page' when users sign in?

We'd like to have 3 different signin forms for our app:
the default signin form, takes them to their normal dashboard (as we do now)
a "foo" signin form that, if they use THAT form to sign in, takes them to a special purpose screen
a "bar" signin form that takes them to yet anogther special-purpose screen
I assume the right approach is to somehow
a) create a new route for /foo and /bar, probably directing both to the SAME signin method but in the route add a url parameter 'signin_type' telling us which "type" of signin form it is?
b) implement a custom RegistrationsController method(s) (what name?) to handle the signin form (we already have a custom new and create method for when they register, since our registration form needed a 'referral code' field added), and have the method look at the url parameter 'signin_type' to redirect the sign to either the normal, or foo, or bad
c) implement another method that handles the signin submit (is that a different method?) that looks at some special embedded form value to figur eout which signing form was used?
That's my best guess. If correct, it's how to do (b) and (c) that has me stumped. Any thoughts will be appreciated!
I think you may be trying to over engineer this. I would approach this with a single sign_in page, and just use conditional logic in overriding the after_sign_in_path_for(resource) method for the Devise controller. Not only will this be much easier to implement now, it will be a lot easier to maintain in the future. Simply add to your ApplicationController.rb:
protected
def stored_location_for(resource)
nil
end
def after_sign_in_path_for(resource)
if condition_foo
redirect_to foo_url
elsif condition bar
redirect_to bar_url
else
redirect_to dashboard_url
end
end
The first method overrides Devise's default location of root and sets it to nil, then the logic after that is pretty self explanatory. This should work for what you are wanting.

Resources