Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Rails/Devise sign-in doesn't work on Safari (422/CSRF error)

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."):

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

like image 960
Yami Medina Avatar asked Dec 07 '16 20:12

Yami Medina


2 Answers

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.

like image 139
Yami Medina Avatar answered Nov 04 '22 17:11

Yami Medina


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
like image 20
vipin Avatar answered Nov 04 '22 18:11

vipin