diff --git a/lib/devise.rb b/lib/devise.rb index 5c4d9c94..b9a21564 100644 --- a/lib/devise.rb +++ b/lib/devise.rb @@ -223,6 +223,10 @@ module Devise mattr_accessor :omniauth_path_prefix @@omniauth_path_prefix = nil + # Set if we should clean up the CSRF Token on authentication + mattr_accessor :clean_up_csrf_token_on_authentication + @@clean_up_csrf_token_on_authentication = true + def self.encryptor=(value) warn "\n[DEVISE] To select a encryption which isn't bcrypt, you should use devise-encryptable gem.\n" end diff --git a/lib/devise/hooks/csrf_cleaner.rb b/lib/devise/hooks/csrf_cleaner.rb new file mode 100644 index 00000000..afec4bc8 --- /dev/null +++ b/lib/devise/hooks/csrf_cleaner.rb @@ -0,0 +1,5 @@ +Warden::Manager.after_authentication do |record, warden, options| + if Devise.clean_up_csrf_token_on_authentication + warden.request.session.try(:delete, :_csrf_token) + end +end diff --git a/lib/devise/models/authenticatable.rb b/lib/devise/models/authenticatable.rb index 261b38c1..313aa7e8 100644 --- a/lib/devise/models/authenticatable.rb +++ b/lib/devise/models/authenticatable.rb @@ -1,4 +1,5 @@ require 'devise/hooks/activatable' +require 'devise/hooks/csrf_cleaner' module Devise module Models diff --git a/lib/generators/templates/devise.rb b/lib/generators/templates/devise.rb index 6b06cad6..d1ab0534 100644 --- a/lib/generators/templates/devise.rb +++ b/lib/generators/templates/devise.rb @@ -76,6 +76,12 @@ Devise.setup do |config| # passing :skip => :sessions to `devise_for` in your config/routes.rb config.skip_session_storage = [:http_auth] + # By default, Devise cleans up the CSRF token on authentication to + # avoid CSRF token fixation attacks. This means that, when using AJAX + # requests for sign in and sign up, you need to get a new CSRF token + # from the server. You can disable this option at your own risk. + # config.clean_up_csrf_token_on_authentication = true + # ==> Configuration for :database_authenticatable # For bcrypt, this is the cost for hashing the password and defaults to 10. If # using other encryptors, it sets how many times you want the password re-encrypted. diff --git a/test/integration/authenticatable_test.rb b/test/integration/authenticatable_test.rb index d9907535..ab33e9c4 100644 --- a/test/integration/authenticatable_test.rb +++ b/test/integration/authenticatable_test.rb @@ -327,6 +327,20 @@ class AuthenticationSessionTest < ActionDispatch::IntegrationTest assert_redirected_to new_user_session_path end + test 'refreshes _csrf_token' do + ApplicationController.allow_forgery_protection = true + + begin + get new_user_session_path + token = request.session[:_csrf_token] + + sign_in_as_user + assert_not_equal request.session[:_csrf_token], token + ensure + ApplicationController.allow_forgery_protection = false + end + end + test 'allows session to be set for a given scope' do sign_in_as_user get '/users'