RLS Multi-Tenant
A Rails gem that provides PostgreSQL Row Level Security (RLS) based multi-tenancy for Rails applications.
📚 Learn more about PostgreSQL Row Level Security: PostgreSQL RLS Documentation
⚠️ IMPORTANT: Database Security Setup for RLS
This gem relies on PostgreSQL Row-Level Security (RLS) for tenant isolation. You MUST create a dedicated database role with proper RLS permissions before using this gem in production.
Features
- 🔒 Row Level Security: Automatic tenant isolation using PostgreSQL RLS
- 🛡️ Security Validation: Prevents running with privileged database users
- 🔄 Context Switching: Easy tenant context management
- 📦 Auto-inclusion: Automatic model configuration
- 🚀 Generators: Rails generators for quick setup
- ⚙️ Configurable: Flexible configuration options
- 🌐 Subdomain Middleware: Automatic tenant switching based on subdomain
Create Application Database Role
Create a dedicated role for your Rails application:
CREATE ROLE app_user
WITH LOGIN
CREATEDB -- can create databases
CREATEROLE -- can create/modify other roles (except superuser)
NOINHERIT -- does not inherit privileges from roles it belongs to
NOREPLICATION -- cannot use replication
NOBYPASSRLS -- cannot bypass Row-Level Security
NOSUPERUSER -- is not a superuser
PASSWORD 'strong_password';If you don't have a database created, you can create one with the new role:
CREATE DATABASE your_db_name OWNER app_user;If you already have a database created, make sure to grant ownership of the database to the new role:
ALTER DATABASE your_db_name OWNER TO app_user;Why This Role Configuration is Critical for RLS
-
NOBYPASSRLS: ESSENTIAL for RLS security - prevents bypassing Row-Level Security policies that enforce tenant isolation -
NOSUPERUSER: Prevents superuser privileges that could compromise RLS policies -
LOGIN: Allows the role to connect to the database -
CREATEDB: Enables database creation for development/testing environments -
CREATEROLE: Allows creating other roles for application-specific users -
NOINHERIT: Ensures the role does not inherit privileges from parent roles -
NOREPLICATION: Prevents the role from being used for replication (security)
Without NOBYPASSRLS, Row-Level Security policies can be bypassed, completely breaking tenant isolation and exposing data across tenants.
Update Database Configuration
Update your config/database.yml to use the new role:
Installation
Add this line to your application's Gemfile:
gem 'rls_multi_tenant'And then execute:
bundle installQuick Start
-
Install the gem configuration:
rails generate rls_multi_tenant:install
-
Configure the gem settings: Edit
config/initializers/rls_multi_tenant.rbto customize your tenant model:RlsMultiTenant.configure do |config| config.tenant_class_name = "Tenant" # Your tenant model class (e.g., "Organization", "Company") config.tenant_id_column = :tenant_id # Tenant ID column name config.enable_security_validation = true # Enable security checks (prevents running with superuser privileges) config.enable_subdomain_middleware = true # Enable subdomain-based tenant switching (default: true) config.subdomain_field = :subdomain # Field to use for subdomain matching (default: :subdomain) config.excluded_subdomains = ['www'] # Subdomains to exclude from tenant lookup (default: ['www']) end
-
Setup the tenant model and migrations:
rails generate rls_multi_tenant:setup
-
Run migrations:
rails db:migrate
Usage
Basic Multi-Tenant Models
Create a new model:
rails generate rls_multi_tenant:model User name emailYour models automatically include the MultiTenant concern:
class User < ApplicationRecord
# Automatically includes MultiTenant concern
include RlsMultiTenant::Concerns::MultiTenant
endTenant Context Switching
# Create a new tenant with subdomain
tenant = Tenant.create!(name: "Company A", subdomain: "company-a")# Switch tenant context for a block
Tenant.switch(tenant) do
User.create!(name: "User from Company A", email: "user@company-a.com") # Automatically assigned to current tenant
end
# Switch tenant context permanently
Tenant.switch!(tenant)
User.create!(name: "User from Company A", email: "user@company-a.com")
Tenant.reset! # Reset context
# Get current tenant
current_tenant = Tenant.currentAutomatic Subdomain-Based Tenant Switching
The gem includes middleware that automatically switches tenants based on the request subdomain. This is enabled by default and works seamlessly with your tenant model.
The middleware automatically:
- Extracts the subdomain from the request host
- Finds the matching tenant by the subdomain field
- Switches the tenant context for the duration of the request
- Resets the context after the request completes
Usage:
# Create tenants with subdomains
tenant1 = Tenant.create!(name: "Company A", subdomain: "company-a")
tenant2 = Tenant.create!(name: "Company B", subdomain: "company-b")
# Users visiting company-a.yourdomain.com will automatically be in tenant1's context
# Users visiting company-b.yourdomain.com will automatically be in tenant2's context
# Users visiting yourdomain.com (no subdomain) will have no tenant contextExcluded Subdomains:
You can configure subdomains that should be excluded from tenant lookup and treated as public access (no tenant context). By default, www is excluded.
RlsMultiTenant.configure do |config|
# Exclude multiple subdomains from tenant lookup
config.excluded_subdomains = ['www', 'admin', 'api']
endWhen a request comes from an excluded subdomain (e.g., www.yourdomain.com), the middleware will:
- Skip tenant lookup
- Treat the request as public access (no tenant context)
- Not raise an error for missing tenant
This is useful for:
- Main website access (
www) - Admin panels that shouldn't be tenant-scoped
- API endpoints that need public access
Public Access (Non-Tenanted Models)
Models that don't include RlsMultiTenant::Concerns::TenantContext are automatically treated as public models and can be accessed without tenant context. This provides a secure, explicit way to separate tenant-specific and public models.
Example:
# Public models (no tenant association)
class PublicPost < ApplicationRecord
# No TenantContext concern included
# These models are accessible without tenant context
end
# Tenant-specific models (automatically generated)
class User < ApplicationRecord
# Automatically includes MultiTenant concern
# These models require tenant context and are constrained by RLS
include RlsMultiTenant::Concerns::MultiTenant
endSecurity Benefits:
-
Explicit Intent: Models must explicitly include
TenantContextto be tenant-constrained - Fail-Safe: Public models are clearly separated from tenant models
- No Configuration Drift: Can't accidentally expose tenant data through misconfiguration
Requirements
- Rails 6.0+
- PostgreSQL 9.5+ (with UUID extension support)
- Ruby 2.7+
UUID Support
This gem uses UUIDs for the tenant model by default to ensure proper multi-tenant isolation. The enable_uuid migration must be run before creating tenant tables.
Contributing
- Fork the repository
- Create your feature branch (
git checkout -b my-new-feature) - Commit your changes (
git commit -am 'Add some feature') - Push to the branch (
git push origin my-new-feature) - Create a Pull Request
License
The gem is available as open source under the terms of the MIT License.