I’ve setup a Rails 5 application with Apartment (1.2.0) and Devise (4.2.0). Due to some DDNS issues there is the constraint that the app is only reachable under app.myapp.com
(note the subdomain app
). myapp.com
redirects to app.myapp.com
.
My use case is that every user (tenant) signed up to the app should access their specific data through their subdomain (e.g. tenant.myapp.com
). The users should not be scoped to their subdomain. Basically it should be possible to sign in from any subdomain. Redirection to the correct subdomain for the tenant is handled by ApplicationController
. As per Devise standard the login page is found at app.myapp.com/users/sign_in
. That’s where the problems start:
The user cannot login because of ”email or password incorrect“ error.
In development I played around a bit. Signing in from lvh.me
works perfectly well. The user is logged in and is redirected to their subdomain. Trying the same with app.lvh.me
leads to the afore mentioned problem.
I’ve set the session store to:
# /config/initializers/session_store.rb
Rails.application.config.session_store :cookie_store, key: '_myapp_session', domain: {
production: '.app.myapp.com',
staging: '.app.myapp.com',
development: '.app.lvh.me'
}.fetch(Rails.env.to_sym, :all)
I’ve also tried the follwing which doesn’t work either:
Rails.application.config.session_store :cookie_store, key: '_myapp_session', domain: :all
What do I need to do so that logging in from any subdomain works?
A test case would be:
User user1
visits the url app.myapp.com/users/sign_in
provides their credentials and is therefor signed in and redirected to user1.myapp.com
.
Bonus: user1
visits the the url another_user.myapp.com/users/sign_in
provides their credentials and is therefor signed in and redirected to user1.myapp.com
.
Other relevant configurations:
# /config/initializers/apartment.rb
config.excluded_models = %w{ User }
config.tenant_names = lambda { User.pluck :subdomain }
config.tld_length = 2
Rails.application.config.middleware.insert_before Warden::Manager, Apartment::Elevators::FirstSubdomain
Apartment::Elevators::FirstSubdomain.excluded_subdomains = ExcludedSubdomains.subdomains
and
# /app/classes/excluded_subdomains.rb
class ExcludedSubdomains
def self.subdomains
%w( www admin test public private staging app web net )
end # subdomains
end # class
and
# /app/models/user.rb
class User < ApplicationRecord
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable,
:confirmable, :lockable
after_create :create_tenant
after_destroy :delete_tenant
# other stuff
def create_tenant
Apartment::Tenant.create(subdomain)
end # create_tenant
def delete_tenant
Apartment::Tenant.drop(subdomain)
end # delete_tenant
end # class
and
class ApplicationController < ActionController::Base
protect_from_forgery with: :exception
before_action :authenticate_user!
before_action :redirect_to_subdomain
private
def redirect_to_subdomain
return if self.is_a?(DeviseController) || self.is_a?(Users::OnboardingController)
if current_user.present? && request.subdomain != current_user.subdomain
redirect_to main_index_url(subdomain: current_user.subdomain)
end # if
end # redirect_to_subdomain
def after_sign_in_path_for(resource_or_scope)
users_onboarding_start_url(subdomain: resource_or_scope.subdomain)
end # after_sign_in_path_for
def after_sign_out_path_for(resource_or_scope)
successful_logout_url(subdomain: '')
end # after_sign_out_path_for
end # class
I’ve found the problem! Instead of prepending the app domain with .app
the domain should have no subdomains prepended but a dot. Like so:
# /config/initializers/session_store.rb
Rails.application.config.session_store :cookie_store, key: '_myapp_session', domain: {
production: '.myapp.com',
staging: '.myapp.com',
development: '.lvh.me'
}.fetch(Rails.env.to_sym, :all)
It was so hard for me to find this simple mistake that I even created an example app. As this example already exists I thought I can share it. It can be found at GitHub.
One can configure the apartment
to exclude certain models from multi-tenancy.
config.excluded_models = ["User"]
The above directive allows you to maintain a common Users across all tenants.
How to scope this user to a particular tenant and redirect them to the correct subdomain needs some re-engineering on devise model and controllers front. You can consider adding additional columns like subdomain
, tenant_name
into User
model for association to a particular client and augment/extend the devise
controllers to register, login and redirect the users correctly.
Instead of your current entry in session_store.rb, try adding the following line in session_store.rb, by replacing .testdomain.com
with your domain name.
Rails.application.config.session_store :cookie_store, key: '_jcrop_app_session', domain: ".testdomain.com"
Though, I did successfully login by using any subdomain like myapp1
or app
, I did not try the redirection part of the code.
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