devise icon indicating copy to clipboard operation
devise copied to clipboard

current_user is a Hash in Rails 8.0.2

Open jasonperrone opened this issue 9 months ago • 11 comments

I upgraded from Rails 8.0.1 to 8.0.2. Noticed the following:

  1. If the user was already logged out when going to the app and tried to login, they are immediately bounced back to the login screen with no error message and no errors in the logs.
  2. If the user already had a live session when I deployed the app with the upgrade from 8.0.1 -> 8.0.2, every action got an error because current_user is coming through ApplicationController as a Hash, not an Object.

LMK if I can give any other information. Ruby 3.4.2 btw.

jasonperrone avatar Mar 26 '25 15:03 jasonperrone

I have the same problem, appears sometimes in my test suite after rails upgrade.

ivanthecrazy avatar Apr 15 '25 05:04 ivanthecrazy

This is what seems to work for me (config/initializers/devise.rb):

Warden::Manager.serialize_into_session do |user|
  user.id
end

Warden::Manager.serialize_from_session do |id|
  User.find(id)
end

ivanthecrazy avatar Apr 15 '25 06:04 ivanthecrazy

Interesting. I'll have to check my config and make sure I'm not doing anything weird.

jasonperrone avatar Apr 15 '25 12:04 jasonperrone

The only thing I've seen is that when running in the production environment, Warden doesn't find any authentication strategies in its config, but it does in development. Still searching.

jasonperrone avatar Apr 15 '25 13:04 jasonperrone

I'm seeing the same thing -- Warden can't find valid mapping

  • Rails 8.02
  • Devise 4.9.4
     Failure/Error: before { sign_in user }
     
     RuntimeError:
       Could not find a valid mapping for #<User id: 1, email: [FILTERED], first_name: "Milford", last_name: "Bergstrom", created_at: "2025-04-16 16:44:38.469535000 +0000", updated_at: "2025-04-16 16:44:38.469535000 +0000", organization_id: 1, role_id: 1>
     # /usr/local/bundle/ruby/3.4.0/gems/devise-4.9.4/lib/devise/mapping.rb:46:in 'Devise::Mapping.find_scope!'
     # /usr/local/bundle/ruby/3.4.0/gems/devise-4.9.4/lib/devise/test/integration_helpers.rb:38:in 'Devise::Test::IntegrationHelpers#sign_in'
     # ./spec/requests/uploads_spec.rb:27:in 'block (4 levels) in <main>'

Adding

RSpec.configure do |config|
  %i(request controller).each do |type|
    config.before(:each, type: type) do
      Rails.application.try(:reload_routes_unless_loaded)
    end
  end

  config.before(:suite) do
    Devise.configure_warden!
  end
end

Warden::Manager.serialize_into_session do |user|
  user.id
end

Warden::Manager.serialize_from_session do |id|
  User.find(id)
end

To my rails_helper.rb fixes it for now.

caseyhelbling avatar Apr 16 '25 16:04 caseyhelbling

So, I did figure something out. When I first upgraded to Rails 8.0.1 there was a bug which affected route loading. As a result, based on the conversation in #5716 , I changed config.reload_routes = false in config/initializers/devise.rb. Well after upgrading to 8.0.2 that configuration broke it. Merely commenting that out as it had been since I've started using Devise fixed this problem.

jasonperrone avatar Apr 18 '25 15:04 jasonperrone

For me, https://github.com/heartcombo/devise/issues/5774#issuecomment-2803988684 is what fixes it (thank you! I spent all day trying to figure this out).

Commenting or showing config.reload_routes = false doesn't impact it.

RyanTG avatar Apr 29 '25 04:04 RyanTG

We encountered intermittent test failures relating to this (in our case, current_admin returning a hash instead of an Admin instance).

A handy rspec --bisect narrowed it down to an RSpec controller block (controller { ... }) running before Rails routes are loaded. RSpec initializes a new RouteSet, triggering #draw and #finalize! before the main app’s routes have loaded. This configured Devise without any devise_for mappings from routes.rb.

Devise builds and caches "mappings" (e.g., for :admin) when devise_for is called in routes.rb. These mappings are used to configure Warden, including session serialization and deserialization.

Devise hooks into ActionDispatch::Routing::RouteSet#finalize! to register these mappings and configure Warden. Configuration can only happen once. If any code triggers #finalize! before Devise registers its mappings (e.g., due to test-specific routing), the Warden configuration is incomplete.

For us, the fix was to explicitly eager load the application routes before the controller test, ensuring Devise mappings are registered before #finalize! is called.

mbaird avatar May 13 '25 09:05 mbaird

I also tracked down the intermittent failure issue to mappings not being loaded yet. Even though Devise.mappings is trying to call the new lazy loaded routes, I found that adding Rails.application.try(:reload_routes_unless_loaded) to a before block in rspec made the flaky tests go away

mattpolito avatar Jun 05 '25 14:06 mattpolito

@mbaird thanks for the details in your last comment. It helped me track a similar issue when a gem used Rails::Engine where it called routes.draw, which caused same issue with Devise when eagerly loaded. Now I'm not sure whether the author of the gem made a mistake by declaring these routes too early, or simply this assumption from Devise devs that config/routes will be loaded first simply doesn't hold 🤔

majkelcc avatar Sep 05 '25 10:09 majkelcc

Are you still running into issues with Devise main? I want to make sure that we're doing the right thing to fix these on Rails 8+. Thanks.

carlosantoniodasilva avatar Oct 31 '25 14:10 carlosantoniodasilva