Auth1BlogMulti-Tenant Auth Architecture
ArchitectureMulti-Tenant· 20 min read

Multi-Tenant Auth Architecture: Lessons from Auth1

Multi-tenancy is the difference between building one SaaS app and building an authentication platform. Every query, every middleware function, every log line must be scoped to a tenant. Miss one query, and User A in Tenant X sees User B in Tenant Y's data.

Problem 1: Email Uniqueness

A global UNIQUE(email) constraint breaks when Alice uses the same email at two tenants. The correct constraint is UNIQUE(app_id, email) — email unique within a tenant, not globally.

SQLschema.sql
CREATE TABLE users (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    app_id VARCHAR(255) NOT NULL,
    email VARCHAR(255) NOT NULL,
    password_hash TEXT,
    UNIQUE(app_id, email)  -- email unique WITHIN a tenant
);

Problem 2: Tenant Context Resolution

Three sources with different trust levels: JWT claim (authoritative, cryptographically bound), request header (can be forged), subdomain (reliable but not proof of access). Priority chain: JWT claim > Header > Subdomain. When both JWT and header are present, optionally validate they match.


Problem 3: The requireTenant Middleware

Some endpoints must have tenant context. A GET /api/users without tenant scope would return users from every tenant — a catastrophic data leak. The requireTenant middleware rejects requests where tenant resolution failed and validates tenant match for authenticated users.


Problem 4: Cross-Tenant Data Leaks in SQL

The most dangerous bug: a missing WHERE clause. Mitigated with query helper functions that enforce app_id scoping, plus PostgreSQL Row-Level Security as a defense-in-depth backup.


Problem 5: Tenant-Scoped Rate Limiting

Rate limit keys include the tenant: userId:tenantId. A tenant with 10,000 active users does not eat into the rate limit headroom of a tenant with 10 users. Every rate limit key — SMS OTP, API, auth — includes the tenant context.


Problem 6: The Fused Middleware Approach

Five separate middleware functions (requestId, authenticate, tenantContext, requireTenant, rateLimiter) replaced with a single native Rust call via auth-shield. JWT verification, tenant resolution, tenant matching, rate limiting, and request ID generation all happen in 4.36 microseconds. Deployed behind a USE_RUST_AUTH=true feature flag.


Problem 7: Session Isolation

A JWT issued for Tenant X must not be valid for Tenant Y, even if the user has accounts in both. tenant_id is embedded in JWT claims at token creation. Refresh tokens are tenant-bound. Cross-tenant token reuse is rejected at the middleware layer.


Architecture Summary

ComponentIsolation Mechanism
User storageUNIQUE(app_id, email) composite index
SQL queriesMandatory app_id in WHERE + RLS backup
Rate limitinguserId:tenantId composite keys in DashMap
Sessions (JWT)tenant_id embedded in JWT claims
Tenant resolutionPriority chain: JWT > Header > Subdomain
MiddlewareFused Rust call resolves tenant + auth in one step

Multi-Tenant Auth, Built In

Tenant isolation at every layer: JWT claims, query scoping, rate limiting, and session management. No configuration required.

Start Free →Read the Docs
Free tier · 1,000 verifications/month · No credit card required