This content originally appeared on DEV Community and was authored by Ítalo Queiroz
Hey devs! 👋
If you've ever found yourself scratching your head trying to implement a system where different organizations (or clients) need their own isolated space, with their own users and configurations, you're probably dealing with multi-tenancy. In this article, I'll share a practical approach to implementing this using Keycloak and NestJS.
The Problem
Imagine you're building a SaaS and each client needs:
- Their own isolated environment
- Manage their own users
- Have their own authentication settings
- Be able to use different identity providers
Sounds complex? Well, that's where our solution with Keycloak and NestJS comes in!
Why Keycloak?
Keycloak is a powerful Identity and Access Management (IAM) tool that provides native support for multi-tenancy through "realms". Each realm is like an isolated mini authentication server, with its own settings, users, and clients.
The Solution
Let's break down our implementation into main parts:
1. Dynamic Realm Creation
First, we need a service that will manage our realms in Keycloak. Here's a simplified example:
@Injectable()
class KeycloakService {
private kcAdminClient: KeycloakAdminClient;
constructor(private configService: ConfigService) {
this.kcAdminClient = new KeycloakAdminClient({
baseUrl: this.configService.get('keycloak.url'),
realmName: 'master'
});
}
async createRealm(config: {
organizationName: string;
displayName: string;
theme?: string;
}) {
await this.authenticate();
const realmConfig = {
realm: config.organizationName,
displayName: config.displayName,
enabled: true,
sslRequired: 'external',
loginTheme: config.theme || 'default',
// Other security settings...
};
await this.kcAdminClient.realms.create(realmConfig);
// Configure OAuth2 clients, default roles, etc...
}
}
2. Tenant Identification Middleware
In each request, we need to identify which tenant (organization) is making the call. A common approach is to use a custom header:
@Injectable()
export class TenantMiddleware implements NestMiddleware {
async use(req: Request, res: Response, next: NextFunction) {
const organizationId = req.headers['x-organization-id'];
if (!organizationId) {
throw new UnauthorizedException('Organization ID is required');
}
req.tenantId = organizationId;
next();
}
}
3. Token Validation per Tenant
Each tenant has their own realm in Keycloak, so we need to validate tokens considering this:
export async function validateToken(token: string, organizationId: string) {
const jwksClient = getJwksClient(organizationId);
try {
const decodedToken = jwt.decode(token, { complete: true });
const key = await jwksClient.getSigningKey(decodedToken.header.kid);
return jwt.verify(token, key.getPublicKey(), {
algorithms: ['RS256'],
issuer: `${KEYCLOAK_URL}/realms/${organizationId}`
});
} catch (error) {
throw new UnauthorizedException('Invalid token');
}
}
4. Keycloak Context Decorator
To facilitate access to the Keycloak admin client in the current tenant context:
export const KeycloakContext = createParamDecorator(
async (data: unknown, ctx: ExecutionContext) => {
const request = ctx.switchToHttp().getRequest();
const organizationId = request.tenantId;
const adminClient = new KeycloakAdminClient({
baseUrl: KEYCLOAK_URL
});
await adminClient.auth({
grantType: 'client_credentials',
clientId: 'admin-cli',
clientSecret: ADMIN_SECRET
});
adminClient.setConfig({ realmName: organizationId });
return {
adminClient,
realm: organizationId
};
}
);
5. Using in a Controller
Now we can put it all together in a controller:
@Controller('users')
export class UserController {
@Post()
async createUser(
@KeycloakContext() kc: KeycloakContext,
@Body() userData: CreateUserDto
) {
const { adminClient, realm } = kc;
// Create user in the tenant-specific realm
const user = await adminClient.users.create({
realm,
username: userData.email,
email: userData.email,
enabled: true,
// other properties...
});
return user;
}
}
Tips and Considerations
Smart Caching: Implement caching for JWT tokens and user information, but remember to separate by tenant!
Data Migration: Have a clear strategy for data migration when creating new tenants.
Monitoring: Add detailed logs to debug tenant-specific issues.
Testing: Create tests that validate isolation between tenants.
Conclusion
Implementing multi-tenancy might seem daunting at first, but with the right tools (like Keycloak and NestJS) and a well-thought-out architecture, we can create a robust and scalable solution.
The code I showed here is just the tip of the iceberg, there's much more to consider like per-tenant database management, distributed caching, per-organization rate limiting, etc. But that's a topic for another article! 😉
This content originally appeared on DEV Community and was authored by Ítalo Queiroz

Ítalo Queiroz | Sciencx (2025-06-27T22:44:55+00:00) Implementing Multi-tenancy with Keycloak and NestJS. Retrieved from https://www.scien.cx/2025/06/27/implementing-multi-tenancy-with-keycloak-and-nestjs/
Please log in to upload a file.
There are no updates yet.
Click the Upload button above to add an update.