I am using Devise 3.2.0 for authentication and found an issue when I do the following:
tab 1: sign in to app
tab 2: go to any page in the app
tab 2: sign out (success)
tab 1: sign out (failure - see exception below)
Exception raised:
ActionController::InvalidAuthenticityToken in Devise::SessionsController#destroy
In the development log I see:
Can't verify CSRF token authenticity
And the top three lines of the stack trace are:
ActionController::InvalidAuthenticityToken (ActionController::InvalidAuthenticityToken):
actionpack (4.0.0) lib/action_controller/metal/request_forgery_protection.rb:163:in `handle_unverified_request'
actionpack (4.0.0) lib/action_controller/metal/request_forgery_protection.rb:170:in `handle_unverified_request'
devise (3.2.0) lib/devise/controllers/helpers.rb:198:in `handle_unverified_request'
How can I ensure successive sign outs don't raise an exception?
Here is whats happening,
When you initially signed out from tab 2, session and authenticity_token associated with the logged in user was destroyed.
When you try to sign out from tab 1, Devise again tries to destroy the session using the authenticity_token which was destroyed on tab 2.
Hence, you get the error ActionController::InvalidAuthenticityToken as devise fails to authenticate using the given authenticity_token.
You only get one unique session per sign in, if that gets destroyed you'll have nothing to destroy again.
EDIT
This behavior is not provided by Devise. If you wish to implement such behavior you will have to override SessionsController.
Create a sessions_controller.rb file in app/controllers/users directory
class Users::SessionsController < Devise::SessionsController
prepend_before_filter :verify_user, only: [:destroy]
private
## This method intercepts SessionsController#destroy action
## If a signed in user tries to sign out, it allows the user to sign out
## If a signed out user tries to sign out again, it redirects them to sign in page
def verify_user
## redirect to appropriate path
redirect_to new_user_session_path, notice: 'You have already signed out. Please sign in again.' and return unless user_signed_in?
end
end
Update routes.rb
devise_for :users, :controllers => { :sessions => "users/sessions" }
A simple solution to this problem could also be allowing sign outs via GET rather than DELETE. In devise.rb you can simply change to:
# The default HTTP method used to sign out a resource. Default is :delete.
config.sign_out_via = :get
paste this in the layout:
<%= csrf_meta_tags %>
If you are still having this issue as i did in Rails 5 and devise 4.4.1, in the app/controllers/application_controller.rb change
protect_from_forgery with: :exception
to
protect_from_forgery with: :null_session
hope it helps.
You can change strategy of verify csrf token.
In rails 3 the default strategy when verify is failed, is return a null session. In rails 4 was changed the strategy in application_controller to return a exception.
I solve this, changing in my application_controller.rb
class ApplicationController < ActionController::Base
- protect_from_forgery, with: :exception
+ protect_from_forgery
This way, use the default strategy.
This bug was fixed in devise 3.3.0.
see the change log for 3.3.0
see the file changes in pull request #2968
note already_signed_out in config/locales/en.yml
Kirti is exactly right. I've had this problem yesterday but with a custom authentication solution. If this is really a problem that you want to fix, you could figure out how to override Devise's signout action and add skip_before_filter :verify_authenticity_token for that action.
Related
Sign-in works fine on Chrome, but doesn't work on Safari (or I assume other Webkit browsers). I get this error message after you sign in ("The change you wanted was rejected. Maybe you tried to change something you didn't have access to."):
According to my heroku logs, this is what's happening:
2016-12-07T14:14:23.778153+00:00 app[web.1]: Can't verify CSRF token authenticity
2016-12-07T14:14:23.778899+00:00 app[web.1]: Completed 422 Unprocessable Entity in 2ms (ActiveRecord: 0.0ms)
2016-12-07T14:14:23.785544+00:00 app[web.1]:
2016-12-07T14:14:23.785547+00:00 app[web.1]: ActionController::InvalidAuthenticityToken (ActionController::InvalidAuthenticityToken):
I believe i'm sending the proper CSRF token, but something seems to be malfunctioning. This is my current application_controller.rb:
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_action :flash_to_headers
# this is so that json requests don't redirect without a user
before_action :authenticate_user!
# before_action :authenticate_user!, unless: request.format == :json
# before_action :user_needed, if: request.format == :json
before_action :set_paper_trail_whodunnit
before_action :set_global_search_variable
def set_global_search_variable
#q = Person.ransack(params[:q])
end
def user_needed
unless current_user
render json: { 'error' => 'authentication error' }, status: 401
end
end
def flash_to_headers
return unless request.xhr?
response.headers['X-Message'] = flash_message if flash_message
response.headers['X-Message-Type'] = flash_type.to_s if flash_type
flash.discard # don't want the flash to appear when you reload page
end
private
def flash_message
[:error, :warning, :notice].each do |type|
return flash[type] unless flash[type].blank?
end
nil
end
def flash_type
[:error, :warning, :notice].each do |type|
return type unless flash[type].blank?
end
nil
end
(Changing protect_from_forgery with: to null_session just causes an endless loop of returning to the login screen.)
This question references a similar problem, but doesn't discuss the complication of Devise. Supposedly Devise handles this issue already, but it somehow isn't working here. Many of these answers are years old, so i'm not sure how relevant they would be today.
I've also tried searching for bugs in the actual Devise Github repo, but I don't seem to be getting anywhere with the suggestions in those threads. Lots of suggestions to edit the application controller, but many times that seems to crash the entire app.
This app runs Ruby 2.2.5 and Rails 4.2.7.1. Would updating to Rails 5 help solve this issue?
It also has an existing (and probably hacky) override for making admin accounts; the person signs up through Devise and then is given admin access through another field called approved manually in the pqsl shell. I'm not sure if that could be related.
The app is on Github, for anyone who wants to take a look:
https://github.com/yamilethmedina/kimball
As it turns out, my problem was solved by this answer. It wasn't in the application controller after all, but in config/initializers/session_store.rb.
This was my initial session_store:
Logan::Application.config.session_store :cookie_store, key: '_cutgroup_session', secure: (Rails.env.production? || Rails.env.staging?)
Upon doing further research, I found this suggestion:
Rails.application.config.session_store :cookie_store, key: "_rails_session_#{Rails.env}", domain: all
This still didn't work; however, it would give a 401 error in the logs (instead of 422) and redirect back to the login page as opposed to showing the error screen I screenshotted above.
Finally, I removed the domain: all part from the end of Rails.application.config.session_store :cookie_store, key: "_rails_session_#{Rails.env}" worked for me on Safari (cookies weren't blocked at any point from the browser). Now, i'm able to log in when the project is deployed on Heroku.
The bounty was a heavy price to pay, but at least the commenters helped me clarify my thinking and find a solution! If someone else comes across this question and comes up with a better one, i'll upvote it instead, but I think i've got it now.
try :
controller/application.rb
protect_from_forgery with: :null_session
and
override you Device controller
sessions_controller.rb
class Users::SessionsController < Devise::SessionsController
skip_before_filter :verify_authenticity_token, :only => [:destroy]
end
I'm trying to implement reset password feature from Devise on my Rails App. It works fine but after sending email instructions it redirects to a wrong URL (api/sessions/new insteadof users/sign_in). Looking on devise code I found that it calls this method to get the url:
# The path used after sending reset password instructions
def after_sending_reset_password_instructions_path_for(resource_name)
new_session_path(resource_name) if is_navigational_format?
end
The resource_name is "user" and the new_session_path("user") is returning the matching route new_session_path on routes.rb file. The expected response should be new_user_session_path.
There is an URL Helper on Devise that should translate new_session_path(:user) to new_user_session_path but it is not working: http://www.rubydoc.org/github/plataformatec/devise/Devise/Controllers/UrlHelpers#generate_helpers%21-class_method
Does anyone know why ? Should I call manually the generate_helpers! method ?
My Rails version is 3.2.14 and Devise 3.2.2.
Best regards !
The problem was that I had a SessionsController which was overriding the Devise SessionsController and the routes paths. After rename my SessionsController It worked fine.
I added :confirmable to my Rails app subsequently. The problem is that when I sign up after adding :confirmable I don't get a notice displayed after the sign up for with telling me what happened, for instance:
You will receive an email with instructions about how to confirm your account in a few minutes.
Why doesn't notice appear and how can I add that notice after adding :confirmable?
Thanks for help
Notice does not appear because the devise is redirecting to your root path which is probably protected by devise authentication. When you hit root_path, you get redirected back to sign_in page (because devise couldnt sign-in the user since it is not activated yet). You can verify that by looking on your development log after you enter user information and hit "sign-up" button - you will see in the log one request for registering a user, then a request navigating to your root url (whatever is in your routes.rb) and then redirect navigation to sign_in page because of authentication.
During redirect all flash messages are lost (since flash messages are only valid for next request only) and when you get redirected from root_path to sign_in page, you are making to requests. So you either need to use flash.keep on the first request before it gets redirected, or change the after_sign_up path so redirection does not happen. I recommend changing after_sign_up path since it's easier and looks as a right way to go about it.
To do that, you need to use your own controller for registrations and add after_sign_up_path method that returns url for redirect:
# app/controllers/registrations_controller.rb
class RegistrationsController < Devise::RegistrationsController
private
def after_inactive_sign_up_path_for(resource)
new_user_session_path
end
end
#config/routes.rb
devise_for :users, :controllers => { :registrations => "registrations" }
I also recommend reading similar question to yours: Rails 3 and Devise: Redirecting to page following signup (confirmable)
I am using cancan, devise, and omniauth in a rails app. I'm quite happy with omniauth, and the method current_user is helpful. Ability class is also helpful. When a user registers, I only get his email from omniauth, and manually create a User for him with some parameters. So I am not using the devise way of creating users.
Consequently, I have no need for :database_authenticatable. Having it prevents me from saving a user without a password. Not having it however, makes users go to '/' instead of '/users/sign_in' when they hit an unauthorized resource.
I would like to not use :database_authenticatable, and have users go to '/users/sign_in' if they are unauthorized. How would I do that?
Note: almost all my controllers have fine-grained authenticate_user! beforefilter, for example:
class UsersController < ApplicationController
before_filter :authenticate_user!, :except => [:index, :index_small, :show,
:new , :sign_in ]
so I'm not sure if I can do anything with before_filter :authenticate_user! in application_controller. If I can get rid of this filter on all the controllers, it would be nice also.
Got it!
In any helper, overwrite sign_in_path
module UsersHelper
def sign_in_path
'/users/sign_in'
end
end
also, most importantly, before_filter :authenticate_user! should be removed completely from controllers, if cancan is used.
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