Author: Vitalii Datsyshyn
Multi-tenant architecture is a one-stop solution for many cases, such as white-labeling a single platform to multiple partners or delivering SaaS applications across industries like fintech, healthcare, and eGaming. However, implementing an efficient and secure multi-tenant architecture is not an easy task.
A common challenges associated with multi-tenant software architecture design are authentication and authorization, two pillars of software security.
In this article, we will explore the implementation of secure authentication in multi-tenant SaaS applications, focusing on OpenIddict as the authentication server framework.
Multi-Tenancy Architecture Patterns
One of the key principles of multi-tenant architecture is isolating the resources of different tenants. There are three essential approaches to isolation: database-level, application-level, and infrastructure-level. Let’s take a closer look at each.
1.Database-level multi-tenancy
There are three primary approaches to database isolation in designing multi-tenant architecture, each with its particular pros and cons.
2.Application-level multi-tenancy
A single application instance serves multiple tenants, with tenant context resolved at runtime. This model involves shared compute resources with logical isolation. It offers a cost-effective solution that necessitates careful security implementation.
3.Infrastructure-level multi-tenancy
Separate application instances per tenant that can utilize container orchestration with Kubernetes. This approach offers the highest isolation but is also the most expensive. It is generally suitable for enterprise customers with strict compliance requirements.
OpenIddict in Multi-Tenant Systems
OpenIddict is an open-source framework that provides tools for implementing OpenID Connect (OIDC) and OAuth 2.0 authentication in .NET application development. It has features that enable efficient authentication and authorization management in multi-tenant applications.
OpenIddict provides a flexible OAuth2/OpenID Connect server implementation for ASP.NET Core. In multi-tenant scenarios, it offers several advantages:
1.Token isolation involves tenant-specific signing certificates, separate token validation per tenant, and custom claims for tenant identification. An example implementation would demonstrate these principles:
services.AddOpenIddict()
.AddServer(options =>
{
options.AddSigningCertificate(GetTenantCertificate(tenantId));
options.RegisterClaims("tenant_id", "tenant_name");
});
2.Dynamic client registration involves creating tenant-specific OAuth clients with isolated client credentials, per-tenant redirect URIs and scopes, and programmatic client management via IOpenIddictApplicationManager.
3.Token introspection endpoints are used for API validation, offering tenant-aware token validation and supporting both reference and self-contained tokens, as demonstrated by the example API configuration.
services.AddOpenIddict()
.AddValidation(options =>
{
options.SetIssuer(GetTenantIssuer(tenantId));
options.UseIntrospection()
.SetClientId(tenant.ClientId)
.SetClientSecret(tenant.ClientSecret);
});
Best Implementation Practices and Tips
To leverage OpenIddict for multi-tenant architecture setups, you need to follow best practices. Here are some professional tips that may help you while facing varying challenges.
1.Tenant resolution
There are many strategies for identifying the tenant context. In particular, the most common ones are:
a) Subdomain-based: tenant1.app.com
b) Path-based: app.com/tenant1
c) Header-based: Custom HTTP headers
d) Token-based: Embedded in JWT claims
Example middleware implementation:
public class TenantMiddleware
{
public async Task InvokeAsync(HttpContext context)
{
var tenant = ResolveTenant(context);
context.Items["TenantInfo"] = tenant;
await _next(context);
}
}
2.Security considerations
While safeguarding your multi-tenant architecture, follow these steps:
Implement tenant isolation at every layer
Validate tenant context in authorization policies
Use separate encryption keys per tenant
Implement comprehensive audit logging
Run regular security assessments per tenant
3.Connection string management
For database-per-tenant architectures, use the following approach:
public class TenantConnectionResolver
{
public string GetConnectionString(string tenantId)
{
// Fetch from secure configuration store
// Cache for performance
// Include fallback mechanisms
return BuildConnectionString(tenantConfig);
}
}
4.Certificate management
To manage certificates efficiently, follow these tips:
Use Azure Key Vault or similar for certificate storage
Implement certificate rotation policies
Separate signing and encryption certificates
Monitor certificate expiration
5.SSO integration
Multi-tenant SSO integration requires the following:
Dynamic SAML/OIDC configuration per tenant
Tenant-specific metadata endpoints
Support for multiple identity providers
Careful session management
Real-World User Authentication Flow Example
Let's examine a complete authentication flow for a user accessing a multi-tenant SaaS application. This example is based on a real implementation we developed for one of our projects.
Scenario: User login and API access
- Initial access: User navigates to https://tenant1.app.com
- Tenant resolution: System identifies "tenant1" from subdomain
- Authentication: User enters credentials on tenant-specific login page
- Token generation: OpenIddict issues tokens with tenant claims
- API access: Blazor WebAssembly app uses tokens to access tenant data
Detailed flow steps for this scenario:
Step 1: User navigation
A user accesses the application through their tenant-specific URL. The system supports multiple access patterns:
Subdomain: tenant1.app.com
Query parameter: app.com?tenant=tenant1
Direct navigation with stored tenant context
Step 2: Tenant middleware processing
// TenantMiddleware extracts tenant information
public async Task InvokeAsync(HttpContext context)
{
var tenantUri = ExtractTenantFromRequest(context);
var tenantInfo = new TenantInfo { TenantBaseUri = tenantUri };
context.Items["TenantInfo"] = tenantInfo;
}
Step 3: Identity Server authentication
The user is redirected to the Identity Server's authorize endpoint:
Parameters include: client_id, redirect_uri, scope, code_challenge (PKCE)
Step 4: Credential validation
The appSignInManager validates credentials with tenant-specific rules. In particular, it:
Checks whether the system is locked for the tenant
Validates password requirements
Enforces password expiry policies
Checks for MFA requirements
Supports impersonation for admin access.
Step 5: Token generation
Upon successful authentication, OpenIddict generates tokens:
{
"sub": "user123",
"name": "John Doe",
"tenant": "tenant1.app.com",
"roles": ["User", "ProjectManager"],
"exp": 1699564800,
"iss": "https://identity.app.com"
}
Step 6: Application access
The Blazor WebAssembly application stores tokens in browser storage, attaches tokens to API requests via AuthorizationMessageHandler, and handles the token refresh automatically.
Step 7: API validation
API endpoints validate tokens through introspection by verifying the token with the Identity Server, extracting tenant context from claims, resolving tenant-specific database connections, and applying tenant-scoped data access.
Additional Flow Variations
Here is an SSO authentication flow relevant to the cases when a tenant has SSO configured:
- A user accesses tenant URL
- System detects SSO configurations for tenant
- System redirects to the external IdP (SAML/OIDC)
- The external IdP authenticates the user
- The system returns to Identity Server with assertion
- Identity Server validates and issues local tokens
- The user accesses the application with tenant context
Here is how the admin impersonation flow for supportive and administrative access looks like:
- The administrator authenticates with master credentials
- The administrator selects target tenant and user
- The administrator uses an impersonation password
- The system adds impersonation claims to token
- All actions are audited with an impersonation flag
- The session has a time-limited access
Let’s also explore a multi-factor authentication flow relevant to the cases when MFA is enabled for a tenant.
- A user enters credentials
- The system validates primary authentication
- The system generates and sends a MFA code
- The user enters a verification code
- The system validates the MFA code
- The system issues tokens with a MFA claim
- Higher-privilege operations check the MFA claim
These are security considerations relevant to the flows:
- PKCE protection: Authorization code flow uses PKCE to prevent code interception.
- Token encryption: Sensitive claims are encrypted in tokens.
- Tenant isolation: Each step validates the tenant context.
- Audit trail: All authentication events are logged with tenant context.
- Rate limiting: This practice protects against brute force attacks per tenant.
- Certificate Rotation: Automated certificate management via Key Vaultю
Conclusions
Implementing authentication in multi-tenant systems requires careful architectural decisions and security considerations. OpenIddict provides a robust foundation for building tenant-aware authentication systems, offering flexibility in token management, client isolation, and integration with existing identity providers. The key to success lies in choosing the appropriate multi-tenancy pattern for your use case and implementing comprehensive security measures at every layer of the application stack.
All in all, the key tips are:
Choose the right multi-tenancy pattern based on security and cost requirements
Implement tenant isolation at the database, application, and authentication layers
Leverage OpenIddict's flexibility for tenant-specific authentication flows
Prioritize security with separate keys, certificates, and comprehensive auditing
Plan for scalability with appropriate caching and connection management strategies
Design authentication flows that balance security with user experience.


Top comments (0)