After a long time digging around in the Devise and Warden source code, I finally found a solution.
Short Answer:
Add this to the User
class:
def self.serialize_from_session(key, salt)
record = to_adapter.klass.unscoped.find(key[0])
record if record && record.authenticatable_salt == salt
end
(Note that I've only tested this for ActiveRecord; if you're using a different ORM adapter you probably need to change the first line of the method... but then I'm not sure if other ORM adapters even have the concept of a "default so
Long Answer:
serialize_from_session
is mixed into the User class from -Devise::Models::Authenticatable::ClassMethods
. Honestly, I'm not sure what it's actually supposed to do, but it's a public method and documented (very sparsely) in the Devise API, so I don't think there's much chance of it being removed from Devise without warning.
Here's the original source code as of Devise 3.4.1:
def serialize_from_session(key, salt)
record = to_adapter.get(key)
record if record && record.authenticatable_salt == salt
end
The problem lies with to_adapter.get(key)
. to_adapter
returns an instance of OrmAdapter::ActiveRecord
wrapped around the User
class, and to_adapter.get
is essentially the same as calling User.find
. (Devise uses the orm_adapter
gem to keep it flexible; the above method will work without modification whether you're using ActiveRecord, Mongoid or any other OrmAdapter-compatible ORM.)
But, of course, User.find
only searches within the default_scope, which is why it can't find my banned users. Calling to_adapter.klass
returns the User
class directly, and from then I can call unscoped.find
to search all my users and make the banned ones visible to Devise. So the working line is:
record = to_adapter.klass.unscoped.find(key[0])
Note that I'm passing key[0]
instead of key
, because key
is an Array (in this case with one element) and passing an Array to find
will return an Array, which isn't what we want.
Also note that calling klass
within the real Devise source code would be a bad idea, as it means you lose the advantages of OrmAdapter
. But within your own app, where you know with certainty which ORM you're using (something Devise doesn't know), it's safe to be specific.