-
Devise subdomain scope has a strategy: https://github.com/heartcombo/devise/wiki/How-to:-Scope-login-to-subdomain
-
This setup let users be scoped for subdomain, like SASS clients
-
Register/login in different subdomains ex.: user1.domain.com, user2.domain.com
-
To use this strategy out of the box with Acts As Tenant gem, we need to adapt it a little
-
acts_as_tenant adds the possibility to use an Account table/model, or in this example a Subdomain table/model
Last active
May 28, 2024 02:22
-
-
Save LEstradioto/8685c23238e75ffde55e83332067a14d to your computer and use it in GitHub Desktop.
devise + acts_as_tenant + subdomain scope
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class ApplicationController < ActionController::Base | |
set_current_tenant_by_subdomain(:subdomain, :subdomain) | |
before_action :authenticate_user! | |
before_action :rewrite_param_names, if: :devise_controller? | |
private | |
# shortcut manner to ALWAYS add subdomain_id in devise requests without needing to patch multiples devises controllers | |
def rewrite_param_names | |
if request.params[:user].present? | |
request.params[:user][:subdomain_id] = request.subdomain | |
end | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class CreateSubdomains < ActiveRecord::Migration[7.1] | |
def change | |
create_table :subdomains do |t| | |
t.string :subdomain, null: false | |
t.timestamps | |
end | |
add_index :subdomains, :subdomain, unique: true | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Rails.application.routes.draw do | |
# constraints to only available/registered subdomains if not redirect to root/guest | |
constraints(lambda { |req| Subdomain.exists?(subdomain: req.subdomain) }) do | |
devise_scope :user do | |
authenticated :user do | |
root "dashboard#show", as: :authenticated_root | |
end | |
unauthenticated do | |
root "devise/sessions#new", as: :unauthenticated_root | |
end | |
end | |
end | |
# in this controller ensure no subdomains are present and redirect | |
root to: "guests#home" | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# frozen_string_literal: true | |
class DeviseCreateUsers < ActiveRecord::Migration[7.1] | |
def change | |
# .......... normal Devise migration | |
# It changes only this part | |
add_reference :users, :subdomain, foreign_key: true | |
add_index :users, [:email, :subdomain_id], unique: true | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class Subdomain < ApplicationRecord | |
has_many :users, dependent: :destroy | |
validates :subdomain, presence: true, uniqueness: true, length: {maximum: 63}, format: {with: /\A[a-z0-9]+(-[a-z0-9]+)*\z/, message: "must be lowercase alphanumeric and can include hyphens"} | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class User < ApplicationRecord | |
acts_as_tenant(:subdomain) | |
# :validatable is not included because Devise subdomain constraint | |
# so we just copy validations from devise module | |
devise :database_authenticatable, :registerable, | |
:recoverable, :rememberable, :confirmable, :lockable, | |
:trackable, authentication_keys: [:email, :subdomain_id] | |
validates :email, presence: true | |
# scope it by subdomain_id | |
validates :email, uniqueness: {scope: :subdomain_id, allow_blank: true, case_sensitive: true, if: :devise_will_save_change_to_email?} | |
validates :email, format: {with: Devise.email_regexp, allow_blank: true, if: :devise_will_save_change_to_email?} | |
validates :password, presence: {if: :password_required?} | |
validates :password, confirmation: {if: :password_required?} | |
validates :password, length: {within: Devise.password_length, allow_blank: true} | |
# patch Devise Subdomain Scoped to work with acts_as_tenant | |
def self.find_for_authentication(warden_conditions) | |
subdomain_id = Subdomain.find_by(subdomain: warden_conditions[:subdomain_id]).id | |
where(email: warden_conditions[:email], subdomain_id: subdomain_id).first | |
end | |
protected | |
def password_required? | |
!persisted? || !password.nil? || !password_confirmation.nil? | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment