Devise and handling the flash - ruby-on-rails

I am using Devise 3.1.1 with rails 3 and I have this flash handling code in my layout:
<% flash.each do |name, msg| %>
<%= content_tag :section, msg, :id => "flash_#{name}", :class => "flash" %>
<% end %>
I sign into my app, flash says:
"Signed in successfully."
then sign out, then sign in incorrectly and flash says:
"Signed out successfully."
"Invalid email or password."
I think I understand why I am getting two messages, when signing in incorrectly there is no redirect, just a render.
Not sure how to fix it though.

I figured out the reason.
When you dig into Devise's source of SessionsController, you'll find #create method as follows:
# POST /resource/sign_in
def create
self.resource = warden.authenticate!(auth_options)
set_flash_message(:notice, :signed_in) if is_navigational_format?
sign_in(resource_name, resource)
respond_with resource, :location => after_sign_in_path_for(resource)
end
In above code, Devise sets flash message for success signed in here. That's what the message you saw as "Signed in successfully.". It uses the method set_flash_message which is just a wrapper of flash[key]= "something". The same is true for #destroy method which show you "Signed out successfully".
Note in above code, there is no code to set error message such as "Invalid password or email". So how comes this message you saw? It is set in Devise::FailureApp
def recall
env["PATH_INFO"] = attempted_path
flash.now[:alert] = i18n_message(:invalid)
self.response = recall_app(warden_options[:recall]).call(env)
end
Note here, the method is flash.now, not flash. The difference is flash.now will deliver flash message in current request, not next.
By default, adding values to the flash will make them available to the next request, but sometimes you may want to access those values in the same request. For example, if the create action fails to save a resource and you render the new template directly, that's not going to result in a new request, but you may still want to display a message using the flash. To do this, you can use flash.now in the same way you use the normal flash. http://guides.rubyonrails.org/action_controller_overview.html#the-flash
So the reason is revealed now.
You signed out. You hit SessionsController#destroy. Devise destroyed your session, brings you to /users/sign_in, render 'new template for your sign in again. The flash object contains the successful signed out message and you saw it.
Now you tried to sign in in same page. This time your form submit hit #create. If error, Devise will not redirect you to anywhere but render the same 'new' template again with the flash.now object which contains sign in error message.
In step 2, you last flash object is not removed because no new request rendered, but another new flash.now object is added. So you see two message.
Solution
Of course it's possible to override Devise to change this behaviour, but that's troublesome and unnecessary.
A more convenient and user-friendly solution is, do not land the user on sign in page after either signed in or signed out.
This is easily by setting store_location and override after_sign_in_path_for and after_signed_out_path_for in your application controller.
def store_location
disable_pattern = /\/users/
session[:previous_url] = request.fullpath unless request.fullpath =~ disable_pattern
end
def after_sign_in_path_for(resource)
session[:previous_url] || root_path
end
def after_sign_out_path_for(resource)
after_sign_in_path_for(resource)
end
By this setting the user will land on his previously browsed page either after signed in or signed out, and they will not see two flash messages in the question again.
The reason is, when the user signed out, he will be redirect to previous page and see the signed out message. When he want to sign in, he need to go to sign in page which is a new request, then the previous signed out flash will be removed.

Related

ng-token-auth and devise token auth confirmation redirect url

I know this has been asked a lot and I've seen many solutions that don't seem to apply to my issue, so I apologize if I missed something.
When sending a confirmation email using Devise's views with ng-token-auth and devise-token-auth the confirmation email link does not have a redirect_url and will show me an error page when clicking on it, however the confirmation api call succeeds.
in ng-token-auth config I've set:
confirmationSuccessUrl: 'http://localhost:8000/#!/login'
in devise_token_auth.rb I've also set:
config.default_confirm_success_url = 'http://localhost:8000/#!/login'
I have also tried overriding the ConfirmationsController as suggested in many other solutions, all to no avail.
I've got a temporary work around by editing the devise view confirmation_instructions.html.erb by adding redirect_url to the confrimation_url call:
confirmation_url(#resource, redirect_url:'http://localhost:8000/#!/login', confirmation_token: #token)
Thanks in advance, and please let me know if I can provide any additional information.
It seems that the link is always sending an HTTP 406 so the object always has an error, but not always a message. For the time being, I've implemented (an ugly) solution by overriding the confirmations controller as such:
def show
self.resource = resource_class.confirm_by_token(params[:confirmation_token])
yield resource if block_given?
if resource.errors.messages.empty?
set_flash_message!(:notice, :confirmed)
#respond_with_navigational(resource){ redirect_to after_confirmation_path_for(resource_name, resource) }
if signed_in?(resource_name)
#signed_in_root_path(root_path)
redirect_to root_path + '#!/users/' + resource.id.to_s
else
redirect_to root_path + '#!/login'
end
else
respond_with_navigational(resource.errors, status: :unprocessable_entity){ render :new }
end
end
This will, at worst, redirect to the login page. Since the confirmations api call is actually working, the user can sign in. If it fails, I can show a flash message indicating they should try to resend the confirmation email.
If anyone knows of a better solution or what I'm doing wrong to cause the HTTP 406 please let me know!

Devise: When resending confirmation email, change flash message

I'm setting up Devise such that users can log in and use the site without having confirmed their email address, similar to this question. But there are a couple of features on the site that users can't use unless they've confirmed.
OK, that's fine. I can check for current_user.confirmed?. If they're not confirmed, I can put a button on the page to have them request the confirmation be sent again.
The issue I'm having is that when they do this while logged in, the flash message they see on the result page is "You are already signed in." Which isn't ideal - I just want to put up the message that the confirmation was sent.
I'm starting down the path of trying to figure out which method of the Devise::ConfirmationController to override, and to what, but I'm hoping someone has done this already.
The reason the flash says "You are already signed in" is because the user is being redirected to new_session_path from the after_resending_confirmation_instructions_path_for method. I would override this method to check if they are logged in. If they are, then don't redirect to new_session_path, set your flash message and redirect to another page.
Override the confirmations controller by putting it in controllers/users/confirmations_controller.rb
class Users::ConfirmationsController < Devise::ConfirmationsController
protected
def after_resending_confirmation_instructions_path_for(resource_name)
if signed_in?
flash[:notice] = "New message here" #this is optional since devise already sets the flash message
root_path
else
new_session_path(resource_name)
end
end
end
Add your confirmationsController to routes->
devise_for :users, :controllers => {:confirmations => 'users/confirmations' }
I think it should look something like this:
module Devise
module ConfirmationsController
extend ActiveSupport::Concern
included do
alias_method_chain :show, :new_flash
end
def show_with_new_flash
# do some stuff
flash[:notice] = "New message goes here"
end
end
end
Could edit
config/locales/devise.en.yml to be more relavent at line:
failure:
already_authenticated: 'You are already signed in.'
or you could do this in your view where flash message has been added
<%=content_tag :div, msg, id: "flash_#{name}" unless msg.blank? or msg == "You are already signed in."%>
I'm using Devise 3.1.0, there's a different method for this scenario instead of after_resending_confirmation_instructions_path_for described in the top voted answer. I modified mine like so:
class Users::ConfirmationsController < Devise::ConfirmationsController
protected
def after_confirmation_path_for(resource_name, resource)
if signed_in?
set_flash_message(:notice, :confirmed)
root_path
elsif Devise.allow_insecure_sign_in_after_confirmation
after_sign_in_path_for(resource)
else
new_session_path(resource_name)
end
end
end

Handle devise 401 gracefully during sign in

I have a very simple sign_in page for a devise user. Upon submitting incorrect data, the log shows a '401 Unauthorized' and redirects me back to the sign_in page. I couldn't figure out a way to show error messages to the user.
I looked at devise::sessions_controller#create which is as follows,
# POST /resource/sign_in
def create
resource = warden.authenticate!(auth_options)
set_flash_message(:notice, :signed_in) if is_navigational_format?
sign_in(resource_name, resource)
respond_with resource, :location => after_sign_in_path_for(resource)
end
def auth_options
{ :scope => resource_name, :recall => "#{controller_path}#new" }
end
The flow gets interrupted at warden.authenticate in case of a failed authentication and the user get redirected to the 'new' which is the sign_in page.
I just need to show the user a invalid_credentials tooltip/flash_message. So I did it by modifying :recall => "#{controller_path}#handle_create_fail" (look at auth_options) which calls handle_create_fails when authentication fails, inside which I setup the error messages.
I am not sure if I overlooked something that devise already provides.
How can I handle this better?
'devise' stores error messages in rails 'flash', using flash[:notice] for success messages and flash[:alert] for problems.
Here's this from the devise documentation:
Remember that Devise uses flash messages to let users know if sign in
was successful or failed. Devise expects your application to call
"flash[:notice]" and "flash[:alert]" as appropriate.
This means that in your view file (or more generally in your application layout file) you should include something similar to these lines:
<%= content_tag(:div, flash[:error], :id => "flash_error") if flash[:error] %>
<%= content_tag(:div, flash[:notice], :id => "flash_notice") if flash[:notice] %>
<%= content_tag(:div, flash[:alert], :id => "flash_alert") if flash[:alert] %>
Here are some similar questions/answers:
Rails - Devise - Error messages when signing in?
Devise errors are displayed twice
devise - Customizing the User Edit Pages
I am not sure if I am understanding you correctly, but by simply placing 'alert' in your view (sign_in.html.erb), Devise will flash the error message.
<div class="alert">
<%= alert %>
</div>
I wanted to be able to do something similar, along the lines of form-specific error messaging like you might have on most Rails forms (as opposed to a layout-wide, global flash).
You can check out what I came up with in this Github gist: https://gist.github.com/3792471
Basically (notes below assume a devise scope of :user):
Add/use a custom FailureApp, used by devise+warden, which simply adds a "scoped" flash "indicator". That is, if devise+warden is exiting quickly due to a failed login and has, therefore, set the flash.now[:alert], add flash.now[:user] = [:alert]
Extend FormBuilder (actually, SimpleForm::FormBuilder in my gist, but should work with others) to add a #flash method, which checks for the scoped indicator(s). If found, the matches are extracted/deleted from the FlashHash and rendered. If not found (that is, flash[:alert] may or may not exist, but flash[:user] either does not exist or does not include :alert), do nothing so the flash remains available for the default use case.
Add rendering to relevant views/layouts.
Note that the layout in my example gist does not have conditional logic around the layout-rendered flash messages. I did that just do I could test to see what does and does not show up under various circumstances.

flash notice is lost on redirect, how to find out what's removing it?

There are many posts on SO about this ( respond_with redirect with notice flash message not working Why is :notice not showing after redirect in Rails 3, among others) , I've read at least 4 and still can't solve this issue.
I've got a portion of my site that lets people do some things before they create an account. I prefer this from a UX perspective. So they're allowed to do X and Y then they get redirected to the "Create account" page (uses Devise).
The redirect looks like:
if userIsNew
... stow information in a cookie to be retrieved later ...
redirect_to "/flash", flash[:notice]
=> "Ok, we'll get right on that after you sign up (we need your email)."
and return # this has to be here, since I'm terminating the action early
end
So "/flash" is a plain page that I made to test this. It doesn't do anything, has no markup of its own, just has the basic html from the application.html, which has this line in the body:
<% if flash[:notice] %>
<p><%= notice %></p>
<% else %>
No notice!
<% end %>
It says 'No notice' every time.
I have tried:
adding in a flash.keep to my before_filter in the static controller
using :notice => instead of flash[:notice] =>
putting the notice in a cookie and pulling that text out of the cookie and into a flash in the before_filter of my application controller
redirect_to :back with the flash[:notice] =>
It's either
flash[:notice] = 'blablabla'
redirect_to foo_url
or
redirect_to foo_url, notice: 'blablabla'
I'm overriding ApplicationController#redirect_to to call flash.keep so that any messages are persisted on redirect without having to explicitly call flash.keep in my controller actions. Works well so far. Haven't had a scenario yet where unwanted messages are persisted.
class ApplicationController < ActionController::Base
def redirect_to(*args)
flash.keep
super
end
end
Let me know if there are any scenarios where this isn't a good solution.
I have been fighting with the same problem for some time and none of the posts seemed to help.
It turns out that - like usually it happens - the the problem was in my code. I did have a "redirect_to" that I forgot about, which was clearing the flash.
Namely, "root_path" for me was served by the StaticPagesController's home method. "home" was doing some checks and then redirecting you to the user_path.
In my code I had in numerous places
redirect_to root_path, :flash => {error: #error}
These redirects were never displaying the flash because my hidden "home" controller serving the "root_path" was making another redirect that cleared the flash.
Therefore my problem was solved when i added the "flash.keep" in my "home" controller method
def home
if current_user
#user = current_user
flash.keep
redirect_to #user unless #user.no_role?
end
end
Faced the same problem, flash just disappeared after any redirect, nothing helped, then, I found that it was switched off...
Check your /config/application.rb for this:
config.middleware.delete ActionDispatch::Flash

Rails: Calling Devise authenticate_user! and handling invalid user/password exception

I have a popup that will only allow to view/save some information if the user is authenticated.
I am using devise.
In the controller before_filter it checks if user is signed in and if not, show a sign in page.
This sign in page is ripped down version of the site's sign in page, so that it fits nicely to the popup.
On the authenticate action I call authenticate_user!. Everything works fine when the user enters valid credentials. But when the credential is invalid, devise automatically redirects to site's sign in page (which as I stated is different and not fit for a popup)
I tried appending a rescue to the call, but to no avail.
Anyone could suggest a better/right way to do this please? :)
def authenticate
authenticate_user! rescue redirect_to "/popup/sign_in"
if user_signed_in?
respond_to do |format|
format.html {
flash[:notice] = I18n.t("logged_in_succesfully")
redirect_back_or_default(accounts_path)
}
else
flash[:error] = I18n.t("devise.failure.invalid")
render "/popup/sign_in"
end
end

Resources