nest-keycloak-connect

repository·master·Indexed 18 days ago

https://github.com/ferrerojosh/nest-keycloak-connect

An adapter for keycloak-nodejs-connect designed for NestJS applications. It enables resource protection using Keycloak's Authorization Services through decorators such as @Roles, @Scopes, and @Resource, and provides guards like AuthGuard, ResourceGuard, and RoleGuard to manage authentication and authorization.

Tokens
8.8K
Snippets
35
Records
39
Agent score
62%

What's inside nest-keycloak-connect

  1. Set up the nest-keycloak-connect example application

    master

    To run the example application, you must first build the main package and then install dependencies within the example directory. A realm configuration file (nest-example.json) is included to facilitate immediate setup in Keycloak.

    # Build the main package from the root directory
    cd ../
    npm run build
    
    # Install dependencies in the example folder
    cd example
    npm install
  2. Configure Auth, Resource, and Role Guards

    master

    You can register guards globally using the APP_GUARD token or scope them to specific controllers using @UseGuards.

    Guard Roles:

    • AuthGuard: Verifies the JWT token or Bearer header. Throws 401 if verification fails.
    • ResourceGuard: Handles controllers with @Resource and methods with @Scopes. It is permissive by default.
    • RoleGuard: Handles methods annotated with @Roles. If policyEnforcement is set to PERMISSIVE, it must be used in conjunction with ResourceGuard.
    // Global registration
    providers: [
      {
        provide: APP_GUARD,
        useClass: AuthGuard,
      },
      {
        provide: APP_GUARD,
        useClass: ResourceGuard,
      },
      {
        provide: APP_GUARD,
        useClass: RoleGuard,
      },
    ];
    
    // Scoped registration
    @Controller('cats')
    @UseGuards(AuthGuard, ResourceGuard)
    export class CatsController {}
  3. Register the KeycloakConnectModule

    master

    To use the library, you must register the KeycloakConnectModule in your NestJS application. You can use synchronous registration, asynchronous registration with a configuration service, or provide a path to a keycloak.json file.

    // Synchronous registration
    KeycloakConnectModule.register({
      authServerUrl: 'http://localhost:8080',
      realm: 'master',
      clientId: 'my-nestjs-app',
      secret: 'secret',
      policyEnforcement: PolicyEnforcementMode.PERMISSIVE,
      tokenValidation: TokenValidation.ONLINE,
    });
    
    // Async registration using a configuration service
    KeycloakConnectModule.registerAsync({
      useExisting: KeycloakConfigService,
      imports: [ConfigModule],
    });
    
    // Registration via keycloak.json file
    KeycloakConnectModule.register(`./keycloak.json`, {
      policyEnforcement: PolicyEnforcementMode.PERMISSIVE,
      tokenValidation: TokenValidation.ONLINE,
    });
  4. Use ResourceGuard for Keycloak Authorization Service permissions

    master

    The ResourceGuard is used to enforce fine-grained permissions via the Keycloak Authorization Service. It is designed to be permissive by default, meaning it only intercepts and enforces policies on routes explicitly annotated with @Resource and @Scopes (or @ConditionalScopes).

    Key Behaviors:

    • Resource Identification: It looks for a resource name provided by the @Resource decorator on either the controller class or the specific method (method level takes precedence).
    • Scope Enforcement: It checks for required scopes using @Scopes or dynamic scopes via @ConditionalScopes. Permissions are evaluated in the format resource:scope.
    • Policy Enforcement Mode: The guard respects the policyEnforcement setting from your configuration. If set to PERMISSIVE, requests to routes without defined resources or scopes are allowed. If set to a restrictive mode, they are denied.
    • Public Routes: Routes marked with @Public are allowed even if no user is present.
    • Claims: By default, it includes http.uri and user.agent in the enforcement context, but this can be customized via @EnforcerOptions.
    // Example conceptual usage
    @Controller('api/data')
    @Resource('my-resource') // Defines the resource name
    export class DataController {
    
      @Get()
      @Scopes(['read', 'write']) // Defines required scopes
      findAll() {
        return 'This is protected by ResourceGuard';
      }
    
      @Public()
      @Get('status') // Bypasses the guard
      getStatus() {
        return 'Public info';
      }
    }
  5. Configure Multi-tenant mode

    master

    Multi-tenancy is configured via the multiTenant option in the module registration. You must provide a realmResolver to determine the realm from the request, and optionally provide resolvers for secrets, client IDs, and auth server URLs.

    {
      authServerUrl: 'http://localhost:8180/',
      clientId: 'nest-api',
      secret: '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];
        },
        realmAuthServerUrlResolver: (realm, request) => {
          const authServerUrls = { master: 'https://master.local/', slave: 'https://slave.local/' };
          return authServerUrls[realm];
        }
      }
    }
  6. Configure RoleMerge and RoleMatch modes

    master

    When using RoleGuard, you can control how roles are aggregated and how they are validated against the user's token.

    RoleMerge (Configuration Option)

    Controlled via the roleMerge property in your KeycloakConnectConfig. It determines how @Roles decorators on the class level and method level interact:

    • RoleMerge.OVERRIDE: The decorator on the specific method takes precedence over the class-level decorator.
    • RoleMerge.ALL: Roles from both the class and the method are combined into a single list.

    RoleMatch (Decorator Option)

    Controlled via the mode property within the @Roles decorator. It determines the logic used to validate the user's token:

    • RoleMatch.ANY: Access is granted if the user has at least one of the required roles.
    • RoleMatch.ALL: Access is granted only if the user has all of the required roles.
  7. Use Keycloak decorators in controllers

    master

    Use the following decorators to enforce authorization and access user data within your NestJS controllers:

    DecoratorDescription
    @KeycloakUserRetrieves the current Keycloak logged-in user.
    @AccessTokenRetrieves the access token used in the request.
    @ResolvedScopesRetrieves the resolved scopes (used in @ConditionalScopes).
    @EnforcerOptionsKeycloak enforcer options.
    @PublicAllows any user to use the route (bypasses guards).
    @ResourceDefines the Keycloak application resource name.
    @ScopesDefines Keycloak application scopes.
    @ConditionalScopesDefines conditional Keycloak application scopes.
    @RolesDefines Keycloak realm/application roles.
  8. Implement KeycloakConfigService

    master

    For asynchronous registration, implement the KeycloakConnectOptionsFactory interface in an injectable service to provide your configuration.

    import { Injectable } from '@nestjs/common';
    import {
      KeycloakConnectOptions,
      KeycloakConnectOptionsFactory,
      PolicyEnforcementMode,
      TokenValidation,
    } from 'nest-keycloak-connect';
    
    @Injectable()
    export class KeycloakConfigService implements KeycloakConnectOptionsFactory {
      createKeycloakConnectOptions(): KeycloakConnectOptions {
        return {
          authServerUrl: 'http://localhost:8080',
          realm: 'master',
          clientId: 'my-nestjs-app',
          secret: 'secret',
          policyEnforcement: PolicyEnforcementMode.PERMISSIVE,
          tokenValidation: TokenValidation.ONLINE,
        };
      }
    }
  9. Configure Multi-Tenant mode with MultiTenantOptions

    master

    To support multiple realms dynamically, use the multiTenant property within your configuration. You must provide a realmResolver and a realmClientIdResolver. You can optionally provide resolvers for the realm secret, the auth server URL, and a flag to always resolve these values.

    const multiTenantConfig: MultiTenantOptions = {
      resolveAlways: true,
      realmResolver: (request) => {
        // Logic to extract realm from request (e.g., header or path)
        return 'tenant-a';
      },
      realmClientIdResolver: async (realm) => {
        // Logic to find client ID for a specific realm
        return `client-${realm}`;
      },
      realmSecretResolver: async (realm) => {
        // Logic to find secret for a specific realm
        return 'secret-for-' + realm;
      },
      realmAuthServerUrlResolver: async (realm) => {
        return `https://keycloak.example.com/${realm}`;
      }
    };
  10. Configure KeycloakConnectConfig for NestJS

    master

    When setting up the KeycloakConnectModule, you provide configuration via KeycloakConnectOptions, which can be a string or a KeycloakConnectConfig object. The KeycloakConnectConfig object is used to define connection details such as the realm, client ID, secret, and server URLs. Note that many keys have both camelCase and kebab-case aliases to maintain compatibility with the original keycloak-nodejs-connect configuration style.

    const config: KeycloakConnectConfig = {
      realm: 'my-realm',
      clientId: 'my-client',
      secret: 'my-client-secret',
      serverUrl: 'https://keycloak.example.com',
      // Additional options like policyEnforcement or tokenValidation can be added here
    };