avatar

An hero

coder by day, an hero by night

Devise Authentication unscoped

At Shop2 we do the Rails 3.2, Mongoid 2.4, and Devise 2.0. We found that Devise by default does not play nice with default scoping.

1
2
3
4
5
6
7
8
9
10
11
12
13
# Our User model with default_scope of active users,
# and a :vip scope
#
class User
  include Mongoid::Document

  field :status

  # now only active users can be authenticated
  default_scope where(status: 'active')

  scope :vip, where(status: 'vip')
end

So how would we authenticate VIPs?

Devise uses Warden::Strategies to authenticate, and loads back user from a session. This is how I plugged into Devise:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
# lib/vip_authenticatable.rb
#
# We introduce :vip_authenticatable strategy to Warden.
# With this strategy your login form might look something like
#
# <form action="/path/to/create/session">
#   <input name="vip[:id]" placeholder="Enter your VIP No.">
#   <input type="submit">
# </form>
#
Warden::Strategies.add(:vip_authenticatable) do
  def valid?
    params[:vip] && params[:vip][:id]
  end

  def authenticate!
    vip = User.vip.find(params[:vip][:id])

    if vip
      success! vip
    elsif !halted?
      fail! :invalid
    end
  end
end

# config/initializers/devise.rb
#
# We now ask Warden to use :vip_authenticatable when authenticating.
#
# Also, we instruct Warden to load User from session without any default scoping.
#
Devise.setup do |config|
  config.warden do |manager|
    require Rails.root+'lib/vip_authenticable'
    manager
      .default_strategies(scope: :user)
        .unshift(:vip_authenticatable)

    manager.serialize_from_session do |keys|
      klass, *args = keys
      user = ActiveSupport::Inflector
        .constantize(klass).unscoped
          .serialize_from_session(*args)
    end
  end
end

And there you have it folks: VIP login.