Cheers!
I use Devise gem for authenticating users and locally (development env) I always get this ActionController::InvalidAuthenticityToken exception on devise::session/create action, no big deal I thought and added some dirt:
class ApplicationController < ActionController::Base
include EmailConcern
# Prevent CSRF attacks by raising an exception.
# For APIs, you may want to use :null_session instead.
protect_from_forgery with: :exception
before_action :authenticate_user!
def handle_unverified_request
true
end
...
end
All right, no more authenticity_token exceptions, I don't mind if it happens only in dev env. But! There is another problem - :authenticate_user! is never worked, so current_user is always nil and I always getting redirected with 401 unauthorized to new session path again. User's credentials are valid and user exists in the DB.
router.rb
Rails.application.routes.draw do
resources :coupons
devise_for :users, path: 'u'
What could be the origin of this issue?
ruby-2.2.2#rails-4.2.0
Related
Context: a Rails app in production, hosted on Heroku, that has around 800 users.
Ruby 2.4.2
Rails 5.1.4
Devise 4.3.0
For some reason, I have seen a few users experience an error:
ActionController::InvalidAuthenticityToken
[GEM_ROOT]/gems/actionpack-5.1.4/lib/action_controller/metal/request_forgery_protection.rb:195
For requests to POST /students/:id/registrations.
It is intermittent, and very few users experience the error.
Clients are Safari 11.0 on iPads.
ApplicationController:
class ApplicationController < ActionController::Base
protect_from_forgery with: :exception
before_action :authenticate_user!, unless: :devise_controller?
before_action :restrict_from_students, unless: :devise_controller?
# ...
end
RegistrationsController:
class RegistrationsController < ApplicationController
skip_before_action :restrict_from_students, only: :create
# ...
end
Is there some scenario (re-POSTing the request, auth timeout but submitting, lack of JS) that would cause this? I cannot seem to reproduce it.
I was having a similar issue.
Use rescue_from in the application controller and redirect somewhere useful with a notification. In my case I attempt to redirect the user back to where they were to reattempt their action, or to the home page as a fallback.
Example for rails 5:
class ApplicationController < ActionController::Base
rescue_from ActionController::InvalidAuthenticityToken,
with: :handle_invalid_token
def handle_invalid_token
redirect_back fallback_location: root_path,
notice: 'Stale session detected'
end
end
Thanks to the rubber duck, I have reproduced the issue.
Sign out
Go "back" to the cached app UI.
Click the button to generate a POST request.
Observe the exception.
The solution here is to use rescue_from to likely redirect the user to the sign in page.
Thank you rubber duckie!
currently I am using the gem delayed-web in my project. I have multiple user roles, and I don't wanna the users whose roles are sales can access to the page delayed web background interface. I already have a method to check for the authentication in my application controller. However, I don't know how to make it work in the route files. Any suggestion would be appreciated.
Updated: I am not using Devise gem. I roll my own authentication.
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
before_action :authenticate
before_action :check_pricer_role, except: [:export_for_customer]
helper_method :check_pricer_role
def check_pricer_role
unless current_user && (current_user.pricer? || current_user.admin?)
redirect_to errors_not_found_path
end
end
end
routes.rb:
Rails.application.routes.draw do
# How to apply the defined authentication here?
mount Delayed::Web::Engine, at: '/jobs'
end
Ok, I've solved the problem. It turns out that I can find the current user based on his authentication token which is saved inside of the Request object(again, I don't use any gems for authentication, I roll my own one, but I don't think that is the problem here anyway).
This is the complete solution, in case somebody way run into the same difficulty:
class AuthConstraint
def matches?(request)
current_user ||= User.find_by_auth_token(request.cookie_jar['auth_token']) if request.cookie_jar['auth_token']
current_user.present? && !current_user.sales?
end
end
Rails.application.routes.draw do
mount Delayed::Web::Engine, at: '/jobs', :constraints => AuthConstraint.new
//Other resources ........
end
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 am using devise to sign up/in.
routes
get 'profile' => 'profile#get_profile'
post 'profile' => 'profile#create_profile'
and profile_controller
def get_profile
render json: {user: current_user}, status: :ok
end
def create_profile
render json: {user: current_user}, status: :ok
end
GET: http://localhost:3000/user/profile returns the expected output. However,
POST request throws an error saying:
ActionController::InvalidAuthenticityToken in User::ProfileController#create_profile.
Please demystify this behavior.
To disable CSRF protection you can edit your ApplicationControllerlike this:
class ApplicationController < ActionController::Base
protect_from_forgery with: :null_session
# ...
end
or disable the CSRF protection for specific controller:
class ProfilesController < ApplicationController
skip_before_action :verify_authenticity_token
# ...
end
:null_session strategy empties the session instead of raising an exception which is perfect for an API. Because the session is empty, you can't use current_user method or othes helpers that refer to the session.
IMPORTANT:
protect_from_forgery with: :null_session must be used only in specific
cases, for example to allow API request (POST/PUT/PATCH/DELETE) without html form
With protect_from_forgery with: :null_session you must restrict access to your data with an authorization system because every one could do request against your API endpoint
Don't remove protect_from_forgery with: :exception for requests that are done through html form, is dangerous! (read here http://guides.rubyonrails.org/security.html#cross-site-request-forgery-csrf)
To handle both standard requests (through html form) and API requests generally you have to set up two different controller for the same resource. Example:
Routes
Rails.application.routes.draw do
resources :profiles
namespace :api do
namespace :v1 do
resources :profiles
end
end
end
ApplicationController
# app/controllers/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
end
ProfilesController
(standard controller for html requests)
# app/controllers/profiles_controller.rb
class ProfilesController < ApplicationController
# POST yoursites.com/profiles
def create
end
end
Api::V1::ProfilesController
(controller for API requests)
# app/controllers/api/v1/profiles_controller.rb
module Api
module V1
class ProfilesController < ApplicationController
# To allow only json request
protect_from_forgery with: :null_session, if: Proc.new {|c| c.request.format.json? }
# POST yoursites.com/api/v1/profiles
def create
end
end
end
end
refereces:
http://api.rubyonrails.org/classes/ActionController/RequestForgeryProtection/ClassMethods.html#method-i-protect_from_forgery
Get requests don't have an authenticity token.
You will have to add the request forgery stuff to your forms using this
<%= csrf_meta_tag %>
And address via javascript
$('meta[name="csrf-token"]')
In ApplicationController (or another controller your controllers inherit from) there's a line:
protect_from_forgery with: :exception
Remove it and CSRF checks will be disabled.
I am trying to get devise/omniauth to properly redirect a user after registering (and later I will be trying with signing in). I am using the omniauth Google gem and basically I want the user once registered to be sent to a plans page. I have read several things here and on other sites and tried every suggestion I can find but having no luck. My applications controller is as follows:
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
private
def stored_location_for(resource)
nil
end
def after_sign_in_path_for(resource)
request.env['omniauth.origin'] || stored_location_for(resource) || root_path
end
def after_sign_up_path_for(resource)
'register#plans' # Or :prefix_to_your_route
end
def after_inactive_sign_up_path_for(resource)
'register#plans' # Or :prefix_to_your_route
end
end
I am not requiring activation but I have tested with inactive sign up as well as sign up just in case. I am not using a custom registration controller or anything, my routes for devise are as follows:
devise_for :users, :controllers => { :omniauth_callbacks => 'users/omniauth_callbacks' }
I also have my plans route registered as such:
get 'register/plans'
I have tested this with rake routes and it is confirmed as:
register_plans GET /register/plans(.:format) register#plans
I am at a loss of what else to try. I am using rails 4.2.4 and Ruby 2.2.1 running on Ubuntu.
Any help would be greatly appreciated.
Many thanks
David