Problem Statement
In a multi-tenant SaaS application, securely managing identities and access across multiple tenants is a complex challenge. Each tenant must operate in complete isolation from others, yet they all share the same underlying infrastructure. This setup requires careful design to ensure that:
- Tenant data and permissions are completely isolated.
- Users can authenticate using both internal and external identity providers (IdPs).
- Role-based access control (RBAC) is enforced consistently across all application components.
- Single Sign-On (SSO) and Federation standards are supported (OAuth 2.0, SAML, etc.).
- API access is secured with proper token validation and tenant context.
This guide will walk through the technical implementation details, trade-offs, and security considerations for building such a system.
Architecture Overview
The architecture must clearly separate tenant-specific data and enforce isolation at every layer. Below is a high-level overview using Mermaid for visualization:
Key Components
- IAM Service: Handles authentication, authorization, and token issuance.
- Tenant Data Store: Stores tenant-specific policies, roles, and user mappings.
- External IdPs: Supports OAuth 2.0, SAML, etc., for federated authentication.
- Application Server: Enforces access control based on tokens and tenant context.
- Token Service: Issues and validates tokens, ensuring tenant context is included.
Tenant Isolation and RBAC Implementation
Tenant isolation is achieved by:
- Storing tenant-specific policies and roles in a separate database schema or using tenant-specific tables.
- Enforcing tenant context in all database queries.
Database Schema Example
-- PostgreSQL schema for tenant isolation
CREATE TABLE tenants (
tenant_id UUID PRIMARY KEY,
tenant_name VARCHAR(255) NOT NULL,
created_at TIMESTAMP WITH TIME ZONE NOT NULL
);
CREATE TABLE tenant_users (
user_id UUID PRIMARY KEY,
tenant_id UUID NOT NULL REFERENCES tenants(tenant_id),
username VARCHAR(255) NOT NULL,
email VARCHAR(255) NOT NULL,
created_at TIMESTAMP WITH TIME ZONE NOT NULL
);
CREATE TABLE roles (
role_id UUID PRIMARY KEY,
tenant_id UUID NOT NULL REFERENCES tenants(tenant_id),
role_name VARCHAR(255) NOT NULL,
permissions JSONB NOT NULL,
created_at TIMESTAMP WITH TIME ZONE NOT NULL
);
RBAC Enforcement
Roles and permissions are stored as JSON objects, allowing for flexible definitions. For example:
{
"role_name": "admin",
"permissions": {
"resource": "tenant_configuration",
"actions": ["read", "write", "delete"]
}
}
Token Structure
Tokens must include tenant context to enforce isolation:
{
"sub": "user-123",
"tenant_id": "tenant-456",
"roles": ["admin", "viewer"],
"exp": 1640000000
}
SSO and Federation
Supporting SSO with OAuth 2.0 and SAML is essential for modern applications.
OAuth 2.0 Implementation
Using OAuth 2.1 (RFC 6749) with OpenID Connect 1.0 (OIDC) is recommended for modern applications.
Authorization Code Flow
Code Snippet for Authorization Server
// Authorization server handler
async function handleAuthRequest(req: Request, res: Response) {
const { client_id, redirect_uri } = req.query;
// Validate client and redirect URI
const client = await getClient(client_id);
if (!client || !client.redirect_uris.includes(redirect_uri)) {
return res.status(400).json({ error: "invalid_client" });
}
// Generate code challenge
const codeVerifier = generateRandomString(64);
const codeChallenge = await generateCodeChallenge(codeVerifier);
// Store code verifier for later validation
await storeCodeVerifier(codeChallenge, codeVerifier);
// Redirect user to authorization page
res.redirect(`/authorize?code_challenge=${codeChallenge}`);
}
SAML Implementation
While SAML 2.0 (RFC 4494) is older, it's still widely used in enterprise environments.
Basic Configuration
<!-- SAML Identity Provider Metadata -->
<EntityDescriptor entityID="https://idp.example.com">
<IDPSSODescriptor protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
<SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="https://idp.example.com/saml/sso"/>
<NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:persistent</NameIDFormat>
</IDPSSODescriptor>
</EntityDescriptor>
WARNING
SAML can be complex to implement correctly. Ensure proper signing of assertions and secure storage of private keys.
Securing API Access
API access must be secured using tokens and proper validation.
JWT Validation
Tokens must be validated at every API endpoint. Below is an example of a validation middleware in Node.js:
// JWT validation middleware
function validateToken(req, res, next) {
const authHeader = req.headers['authorization'];
if (!authHeader) {
return res.status(401).json({ error: 'Unauthorized' });
}
const token = authHeader.split(' ')[1];
if (!token) {
return res.status(401).json({ error: 'Unauthorized' });
}
// Validate JWT
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
req.user = decoded;
next();
} catch (error) {
console.error('Token validation failed:', error);
return res.status(401).json({ error: 'Invalid token' });
}
}
API Gateway Configuration
Use an API gateway (e.g., AWS API Gateway, Kong) to enforce token validation and tenant context.
Example Kong Configuration
services:
- name: tenant-api
url: http://tenant-api:8080
routes:
- name: tenant-api-route
paths: /api/tenant
methods: GET
plugins:
- name: jwt
config:
secret: "your_jwt_secret"
claims_to_verify: ["tenant_id"]
Trade-offs and Considerations
Trade-offs
- OAuth vs. SAML:
- OAuth 2.0: Simpler to implement, widely supported, but requires proper handling of code flows.
- SAML: More secure in some cases but complex to implement and maintain.
- RBAC vs. ABAC:
- RBAC: Simpler to implement but less flexible.
- ABAC: More flexible but harder to maintain.
- Multi-Tenant Database Design:
- Shared Database: Easier to implement but harder to isolate.
- Tenant-Specific Databases: More isolated but harder to manage.
Security Considerations
- Token Expiration:
- Tokens must have a short expiration (e.g., 15 minutes) and support refresh tokens.
- Implement proper revocation mechanisms.
- Certificate Management:
- Use certificate pinning for SAML and OAuth.
- Rotate certificates regularly.
- Audit Logging:
- Log all authentication and authorization attempts.
- Implement proper retention policies.
Common Mistakes and How to Avoid Them
- Improper Tenant Isolation:
- Always include tenant context in database queries.
- Use tenant-specific schemas or tables.
- Insecure Token Storage:
- Store tokens securely (e.g., HTTP Only cookies).
- Use secure token formats (e.g., JWT with HS256).
- Misconfigured SSO:
- Validate all SSO requests.
- Use proper encryption for SAML assertions.
Gotchas and Real-Life Lessons
- Token Expiration Gotcha:
- Tokens that expire too quickly can cause user frustration.
- Tokens that don't expire can be security risks.
- Tenant Data Leakage:
- Always validate tenant context in API requests.
- Use proper database permissions to enforce isolation.
- Certificate Rotation:
- Automate certificate rotation.
- Test certificate changes thoroughly.
Conclusion
Building a secure multi-tenant IAM system requires careful design and implementation. By following the guidelines in this article, you can ensure that your system is both secure and scalable. Remember to always prioritize security and thoroughly test your implementation.