Skip to content
Kapil Patel edited this page Nov 18, 2024 · 5 revisions

One of the best features of Devise is its ability to provide a complete set of functionality for multiple types of Users.

Now we could combine Devise with some role ability gem so that we have a database attribute that checks which type of user is signing in. There are advantages to this approach.

However, creating a single devise model for each type gives your application more flexibility and feels natural. By doing this we are able to have sign up, log in, forget password, mailers, confirmation instructions all specific to that type of user with the namespace included.

This stackoverflow answer inspired the new version for this page: https://stackoverflow.com/questions/37145991/using-devise-for-multiple-models Credit and thanks go to: https://stackoverflow.com/users/4509585/pyfl88

After following the docs on how to get Devise up and running (don't ignore any of the steps) do:

1. Generate multiple devise models.

For this example we'll be creating a User model and a Admin model

rails generate devise user
rails generate devise admin

2. Name your routes

devise_for :users, path: 'users'
# eg. http://localhost:3000/users/sign_in
devise_for :admins, path: 'admins'
# eg. http://localhost:3000/admins/sign_in

3. If you want scoped views

# config/initializers/devise.rb
config.scoped_views = true
# run
rails g devise:views users
rails g devise:views admins

4. Expose controllers to further customize them

This will become important in Section 6

rails generate devise:controllers users
rails generate devise:controllers admins

5. Modify your routes to reflect your changes

devise_for :users, path: 'users', controllers: { sessions: "users/sessions" etc....}
devise_for :admins, path: 'admins', controllers: { sessions: "admins/sessions" etc....}

6. Fix cross model visits (fancy name for: Users can visit admins login and viceversa and mess up your auth tokens)

ActionController::InvalidAuthenticityToken happens when a logged in Devise model (eg. User) logs in, in the same browser, as another Devise Model (eg. Admin)

A technical issue with this type of setup, however, is that a signed in devise user will be able to access sign in and login pages of another devise user. This is definitely not a desired result.

As a solution, a simple concern can be implemented. Here it is called, Accessible.

# ../controllers/concerns/accessible.rb
module Accessible
  extend ActiveSupport::Concern
  included do
    before_action :check_user
  end

  protected
  def check_user
    if current_admin
      flash.clear
      # if you have rails_admin. You can redirect anywhere really
      redirect_to(rails_admin.dashboard_path) and return
    elsif current_user
      flash.clear
      # The authenticated root path can be defined in your routes.rb in: devise_scope :user do...
      redirect_to(authenticated_user_root_path) and return
    end
  end
end

Here we are redirecting and returning any user that is trying to access pages that are not specific to the type of user they are. Including this concern in your Sessions and Registrations Controller while performing this check as a before_action on your new methods is one simple way to setup multiple users for devise.

7. Include your freshly baked concern in the needed devise controllers

# eg. ../controllers/admins/sessions_controller.rb
# eg. ../controllers/admins/registrations_controller.rb
# eg. ../controllers/users/sessions_controller.rb
# eg. ../controllers/users/registrations_controller.rb
include Accessible

Note:

You must skip_before_action for the destroy action in each SessionsController to prevent the redirect to happen before the sign out occurs.

class Admins::SessionsController < Devise::SessionsController
  include Accessible
  skip_before_action :check_user, only: :destroy
  # ...
end

You must also skip_before_action for the edit, update, destroy, and cancel actions in each RegistrationsController to allow current users to edit and cancel their own accounts. Otherwise they will be redirected before they can reach these pages.

class Admins::RegistrationsController < Devise::RegistrationsController
  include Accessible
  skip_before_action :check_user, except: [:new, :create]
  # ...
end

Doing this won't allow other types of users to access these pages, as if another type of user is signed in and tries to access the admin user edit page for example, they'll be redirected to the admin user login page, which will then redirect to your specified route thanks to Accessible.

8. Setting custom flash messages per resource

Devise uses devise.en.yml to determine which flash messages to display based on the action a user is performing. We can customise these messages per scope.

en:
  devise:
    confirmations:
      admin_user:
        confirmed: "Your ADMIN email address has been successfully confirmed."
      confirmed: "Your email address has been successfully confirmed."

If an admin_user confirms their email address, in this instance they will see a custom flash message. All other user types will see the default message.

Clone this wiki locally