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_idonUser.find_for_database_authentication, and avoid the JOIN. I don't know how to do it, assubdomainis handled automagicaly by Devise/Warden.warden_conditionskeys should beemailandaccount_id.