TL;DR: in a multi-tenant app with Devise, a user should be able to register with the same email on different tenants/accounts. How can I make Devise use the account_id in User.find_for_database_authentication?
I have a multi-tenant app. Each subdomain belongs to an account, with many users and admins (different tables, no inheritance between them).
class User < ApplicationRecord
belongs_to :account
devise :database_authenticatable, :confirmable, :recoverable, :rememberable
validates :email, uniqueness: true
end
class AdminUser < ApplicationRecord
belongs_to :account
devise :database_authenticatable, :confirmable, :recoverable, :rememberable
end
# config/routes.rb
scope module: 'auth' do
devise_for :users, path: ''
devise_for :admins, path: 'admin', class_name: 'AdminUser', ...
end
# /sign_in for users, /admin/sign_in for admins
# (you can log as both at the same time)
Thinks worked well, but I need to allow a user to sign in with the same email on different tenants/accounts. First, I fixed the validation:
class User < ApplicationRecord
validates :email, uniqueness: { scope: :account_id }
end
The problem is that when a user sign up / log in, Devise will search for the first user with some email, regardless of the account_id, so if I share the same email address on subdomain1 and subdomain2, when I log into subdomain2, I get the user info from subdomain1 (which is wrong)
Devise documentation recommends to config request_keys and redefine User.find_for_database_authentication
# config/initializers/devise.rb
config.request_keys = [:subdomain]
# app/models/user.rb
def self.find_for_database_authentication warden_conditions
joins(:account).where(
email: warden_conditions[:email],
accounts: {subdomain: warden_conditions[:subdomain]}
).first
end
This kind of works, but I found two problems/disadvantages:
When I log out with the user, it does the same with the admin (kind of annoying for development)
I'd like to use account_id on User.find_for_database_authentication, and avoid the JOIN.
I don't know how to do it, as subdomain is handled automagicaly by Devise/Warden. warden_conditions keys should be email and account_id.
According to this Wiki
If you are using column name other than subdomain to scope login to subdomain, you may have to use authentication_keys. For example, if you have subdomains table and are using subdomain_id on your Devise model to scope User, you will have to add authentication_keys: [:email, :subdomain_id]
So I think you should
devise :database_authenticatable, :confirmable, :recoverable, :rememberable, authentication_keys[:account_id]
In addition, I think you should redefine find_first_by_auth_conditions instead of find_for_database_authentication in order to support password recovery
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With