Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
### Unreleased

* bug fixes
* Fix `Devise::FailureApp` not committing the CSRF token to the session on Rails 7.1+, which dropped the session and CSRF token on authentication failure. [#5851](https://github.com/heartcombo/devise/pull/5851)

### 5.0.4 - 2026-05-08

* security fixes
Expand Down
6 changes: 6 additions & 0 deletions lib/devise/failure_app.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@ class FailureApp < ActionController::Metal
include ActionController::UrlFor
include ActionController::Redirecting

# Rails 7.1+ defers writing the CSRF token to the session and commits it at
# the end of the request through the controller instance. FailureApp becomes
# that instance, so it needs +commit_csrf_token+ from RequestForgeryProtection
# to avoid silently dropping the token (and the session) on auth failure.
include ActionController::RequestForgeryProtection

include Rails.application.routes.url_helpers
include Rails.application.routes.mounted_helpers

Expand Down
15 changes: 15 additions & 0 deletions test/failure_app_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -479,4 +479,19 @@ def call_failure(env_params = {})
assert_equal 'http://test.host/users/sign_in', @response.second['Location']
end
end

# Only on Rails 7.1+, where the CSRF token is deferred and committed via the controller instance.
if ActionDispatch::Request.method_defined?(:commit_csrf_token)
context "Committing the CSRF token" do
test "stores the deferred CSRF token in the session" do
csrf_token = "a-csrf-token"
request = ActionDispatch::Request.new(
"rack.session" => {},
ActionController::RequestForgeryProtection::CSRF_TOKEN => csrf_token
)
Devise::FailureApp.new.commit_csrf_token(request)
assert_equal csrf_token, request.session[:_csrf_token]
end
end
end
end