Skip to content

Commit

Permalink
Add realmClientIdResolver
Browse files Browse the repository at this point in the history
  • Loading branch information
ferrerojosh committed Oct 18, 2024
1 parent d55cf07 commit 7499d4e
Show file tree
Hide file tree
Showing 4 changed files with 68 additions and 13 deletions.
29 changes: 17 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ An adapter for [keycloak-nodejs-connect](https://github.com/keycloak/keycloak-no

</div>


## Features

- Protect your resources using [Keycloak's Authorization Services](https://www.keycloak.org/docs/latest/authorization_services/).
Expand Down Expand Up @@ -214,14 +213,14 @@ export class ProductController {

Here is the decorators you can use in your controllers.

| Decorator | Description |
| ------------------ | --------------------------------------------------------------------------------------------------------- |
| @AuthenticatedUser | Retrieves the current Keycloak logged-in user. (must be per method, unless controller is request scoped.) |
| @EnforcerOptions | Keycloak enforcer options. |
| @Public | Allow any user to use the route. |
| @Resource | Keycloak application resource name. |
| @Scopes | Keycloak application scope name. |
| @Roles | Keycloak realm/application roles. |
| Decorator | Description |
| ---------------- | --------------------------------------------------------------------------------------------------------- |
| @KeycloakUser | Retrieves the current Keycloak logged-in user. (must be per method, unless controller is request scoped.) |
| @EnforcerOptions | Keycloak enforcer options. |
| @Public | Allow any user to use the route. |
| @Resource | Keycloak application resource name. |
| @Scopes | Keycloak application scope name. |
| @Roles | Keycloak realm/application roles. |

## Multi tenant configuration

Expand All @@ -230,17 +229,22 @@ Setting up for multi-tenant is configured as an option in your configuration:
```typescript
{
// Add /auth for older keycloak versions
authServerUrl: 'http://localhost:8180/',
clientId: 'nest-api',
secret: 'fallback', // will be used as fallback when resolver returns null
authServerUrl: 'http://localhost:8180/', // will be used as fallback
clientId: 'nest-api', // will be used as fallback
secret: 'fallback', // will be used as fallback
multiTenant: {
resolveAlways: true,
realmResolver: (request) => {
return request.get('host').split('.')[0];
},
realmSecretResolver: (realm, request) => {
const secrets = { master: 'secret', slave: 'password' };
return secrets[realm];
},
realmClientIdResolver: (realm, request) => {
const clientIds = { master: 'angular-app', slave: 'vue-app' };
return clientIds[realm];
},
// note to add /auth for older keycloak versions
realmAuthServerUrlResolver: (realm, request) => {
const authServerUrls = { master: 'https://master.local/', slave: 'https://slave.local/' };
Expand Down Expand Up @@ -276,6 +280,7 @@ For Keycloak options, refer to the official [keycloak-connect](https://github.co
| realmResolver | A function that passes a request (from respective platform i.e express or fastify) and returns a string | yes | - |
| realmSecretResolver | A function that passes the realm string, and an optional request and returns the secret string | no | - |
| realmAuthServerUrlResolver | A function that passes the realm string, and an optional request and returns the auth server url string | no | - |
| realmClientIdResolver | A function that passes the realm string, and an optional request and returns the client-id string | no | - |

## Example app

Expand Down
7 changes: 7 additions & 0 deletions src/interface/keycloak-connect-options.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,13 @@ export interface MultiTenantOptions {
realm: string,
request?: any,
) => Promise<string> | string;
/**
* The realm client id resolver function.
*/
realmClientIdResolver: (
realm: string,
request?: any,
) => Promise<string> | string;
}

/**
Expand Down
2 changes: 1 addition & 1 deletion src/keycloak-connect.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import {
import { KeycloakMultiTenantService } from './services/keycloak-multitenant.service';

export * from './constants';
export * from './decorators/authenticated-user.decorator';
export * from './decorators/keycloak-user.decorator';
export * from './decorators/enforcer-options.decorator';
export * from './decorators/public.decorator';
export * from './decorators/resource.decorator';
Expand Down
43 changes: 43 additions & 0 deletions src/services/keycloak-multitenant.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ export class KeycloakMultiTenantService {

const authServerUrl = await this.resolveAuthServerUrl(realm, request);
const secret = await this.resolveSecret(realm, request);
const clientId = await this.resolveClientId(realm, request);

// Check if existing
if (this.instances.has(realm)) {
Expand All @@ -57,6 +58,7 @@ export class KeycloakMultiTenantService {

keycloak.config.authServerUrl = authServerUrl;
keycloak.config.secret = secret;
keycloak.config.clientId = clientId;
keycloak.grantManager.secret = secret;

// Save instance
Expand All @@ -73,6 +75,7 @@ export class KeycloakMultiTenantService {
authServerUrl,
realm,
secret,
clientId,
});
const keycloak: any = new KeycloakConnect({}, keycloakOpts);

Expand Down Expand Up @@ -135,6 +138,46 @@ export class KeycloakMultiTenantService {
);
}

async resolveClientId(
realm: string,
request: any = undefined,
): Promise<string> {
if (typeof this.keycloakOpts === 'string') {
throw new Error(
'Keycloak configuration is a configuration path. This should not happen after module load.',
);
}
if (
this.keycloakOpts.multiTenant === null ||
this.keycloakOpts.multiTenant === undefined
) {
throw new Error(
'Multi tenant is not defined yet multi tenant service is being called.',
);
}

// If no realm client-id resolver is defined, return defaults
if (!this.keycloakOpts.multiTenant.realmClientIdResolver) {
return this.keycloakOpts.clientId || this.keycloakOpts['client-id'];
}

// Resolve realm client-id
const resolvedClientId =
this.keycloakOpts.multiTenant.realmClientIdResolver(realm, request);
const realmClientId =
resolvedClientId || resolvedClientId instanceof Promise
? await resolvedClientId
: resolvedClientId;

// Override client-id
// Order of priority: resolved realm secret > default global secret
return (
realmClientId ||
this.keycloakOpts.clientId ||
this.keycloakOpts['client-id']
);
}

async resolveSecret(
realm: string,
request: any = undefined,
Expand Down

0 comments on commit 7499d4e

Please sign in to comment.