Multi Tenant Support#2392
Open
fadlikadn wants to merge 5 commits intoteamhanko:mainfrom
Open
Conversation
- Introduced multi-tenant configuration in `hanko.config.json` with options for enabling multi-tenant mode, specifying tenant headers, and auto-provisioning tenants. - Implemented `Tenant` middleware to resolve tenant from HTTP headers and auto-provision tenants if enabled. - Added `Tenant` model and persister for managing tenant data in the database. - Updated existing models (User, Email, Username, etc.) to include `tenant_id` for tenant-scoped data. - Enhanced user and email persisters to support tenant-specific queries. - Created migration scripts for adding tenants table and modifying existing tables to include tenant_id. - Updated webhook events to include tenant-related events (create, update, delete).
294a7da to
0f90314
Compare
This file contains hidden or 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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
This PR adds comprehensive multi-tenant support to Hanko, enabling the same email/username to be registered across multiple tenants with independent authentication credentials (passwords, passkeys, MFA). This architecture give flexibility for teams to handle tenant management and fit with various needs. This implementation only limited to handle tenant ID since I believe we can handle role and other areas in separated system (out of Hanko scope) based on needs.
Key Features:
X-Tenant-ID)tenant_idclaim for downstream servicesUse Case:
This enables SaaS platforms to use a single Hanko instance for multiple customer organizations, where:
user@example.comin Tenant A is a different account thanuser@example.comin Tenant BImplementation
Architecture Overview
Database Schema Changes
New Table:
tenantsAdded
tenant_idColumn To:usersemailsusernamesidentitieswebauthn_credentialsotp_secretspassword_credentialssessionsaudit_logsflowsUniqueness Strategy
Uses PostgreSQL partial unique indexes to allow:
tenant_id IS NULL(backward compatibility)New Files
backend/config/config_multi_tenant.gobackend/persistence/models/tenant.gobackend/persistence/tenant_persister.gobackend/middleware/tenant.gobackend/handler/tenant_admin.gobackend/dto/admin/tenant.gobackend/persistence/migrations/20260120000001_create_tenants.*.fizzbackend/persistence/migrations/20260120000002_add_tenant_id.*.fizzbackend/persistence/migrations/20260120000003_change_unique_constraints.*.fizzModified Files
Configuration
backend/config/config.go- AddedMultiTenantfieldbackend/config/config_default.go- Added default multi-tenant configbackend/json_schema/hanko.config.json- Regenerated to include multi-tenant schemaModels (Added
TenantIDfield)backend/persistence/models/user.gobackend/persistence/models/email.gobackend/persistence/models/username.gobackend/persistence/models/identity.gobackend/persistence/models/webauthn_credential.gobackend/persistence/models/otp_secret.gobackend/persistence/models/password_credential.gobackend/persistence/models/session.gobackend/persistence/models/audit_log.gobackend/persistence/models/flow.goPersisters (Added tenant-aware methods)
backend/persistence/persister.go- AddedGetTenantPersister()interfacebackend/persistence/email_persister.go- AddedFindByAddressAndTenant()backend/persistence/username_persister.go- AddedGetByNameAndTenant()backend/persistence/user_persister.go- AddedGetByEmailAddressAndTenant(),GetByUsernameAndTenant()Flow API (Tenant-aware authentication)
backend/flow_api/flow/shared/flow.go- AddedTenantIDandTenantto Dependenciesbackend/flow_api/handler.go- Pass tenant context to flow dependenciesbackend/flow_api/flow/registration/action_register_login_identifier.go- Tenant-scoped email/username lookupbackend/flow_api/flow/registration/hook_create_user.go- Set tenant_id on created entitiesbackend/flow_api/flow/credential_usage/action_continue_with_login_identifier.go- Tenant-scoped loginbackend/flow_api/flow/credential_usage/action_password_login.go- Tenant-scoped password verificationSession & JWT
backend/session/session.go- Addedtenant_idclaim to JWTbackend/dto/user.go- AddedTenantIDtoUserJWTstructWebhooks
backend/webhooks/events/events.go- AddedTenant,TenantCreate,TenantUpdate,TenantDeleteeventsRouters
backend/handler/public_router.go- Applied tenant middlewarebackend/handler/admin_router.go- Added tenant management routesConfiguration Options
Admin API Endpoints
/tenants/tenants/tenants/:id/tenants/:id/tenants/:idTradeoffs & Design Decisions
Nullable
tenant_id: Allows backward compatibility with existing single-tenant deployments. Global users (tenant_id = NULL) continue to work.Partial Unique Indexes: Used PostgreSQL-specific partial indexes for tenant-scoped uniqueness. This is the most efficient approach but ties us to PostgreSQL (and compatible databases like CockroachDB).
Auto-Provisioning: Enabled by default for ease of use in development. Production deployments may want to disable this and manage tenants explicitly via Admin API.
CASCADE Delete: When a tenant is deleted, all associated users, credentials, and sessions are deleted. This is intentional for clean tenant removal.
Header-Based Tenant ID: Using HTTP header (vs URL path or query param) keeps the API surface clean and allows the same endpoints for all tenants.
Tests
Manual Testing
Prerequisites
Test 1: Backward Compatibility (Multi-tenant Disabled)
Test 2: Enable Multi-tenant and Auto-provision
Test 3: Verify Tenant Admin API
Test 4: Verify JWT Contains tenant_id
After successful login with tenant header, decode the JWT:
Test 5: Database Verification
Additional Context
Configuration Example
JWT Token with Tenant
When multi-tenant is enabled and a user authenticates within a tenant context, the JWT includes:
{ "sub": "550e8400-e29b-41d4-a716-446655440000", "tenant_id": "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa", "email": "user@example.com", "iat": 1705708800, "exp": 1705752000 }Webhook Events
New webhook events for tenant lifecycle:
tenant.create- Fired when a tenant is created (including auto-provisioning)tenant.update- Fired when a tenant is updatedtenant.delete- Fired when a tenant is deletedBreaking Changes
None. This is a fully backward-compatible change: