npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@edirect/auth

v9.6.0

Published

Auth Module

Downloads

359

Readme

@edirect/auth

The EDirectInsure Auth Module.

Installation

npm i @edirect/auth

Basic use

The auth-module was designed to support AuthService and Keycloak access tokens. With this module, it is possible to configure the authentication and authorization of any endpoint of your microservice. Below is an example containing everything needed for this.

Module settings

import { Module, NestModule, MiddlewareConsumer } from '@nestjs/common';
import { APP_GUARD } from '@nestjs/core';
import { AuthGuard, AuthMiddleware } from '@edirect/auth';

@Module({
  imports: [
    //... your modules
    AuthModule,
  ],
  providers: [
    //... your providers
    {
      provide: APP_GUARD,
      useFactory: (configService: ConfigService, reflector: Reflector) => new AuthGuard(configService, reflector),
      inject: [ConfigService, Reflector]
    },
  ],
})
export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer): void {
    consumer.apply(AuthMiddleware).forRoutes('cats');
  }
}

In this code snippet above, the APP_GUARD provider will make the authorization check available to any endpoint of your microservice, however, to enable it, you need to add AuthMiddleware to the route you intend to configure the authorization check (in this case, the cats route).

Controller settings

// cats.controller.ts
import { Controller, Get } from '@nestjs/common';
import { Roles, Permissions } from '@edirect/auth';

@Controller('cats')
export class CatsController {
  @Get()
  @Resources('Default Resource')
  @Roles('ROLE_USER')
  @Permissions('CATS.READ')
  findAll(): Promise<Response> {
    return [];
  }
}

In this code snippet above, three notations used to verify the authorization of the GET /cats endpoint were configured, let's understand them:

  • @Resources - (Optional) Defines the name of the resource configured on the Resource Server associated with your microservice's client in Keycloak. It should only be set if you are using this feature.
  • @Roles - (Optional) Defines one or more roles that your microservice user or client must have to be able to access the endpoint. This will be used for a static check on the access token or dynamic by Keycloak if the @Resource annotation has been defined.
  • @Permissions - (Optional) Defines one or more permissions that your microservice user or client must have to be able to access the endpoint. This will be used for a static check on the access token or dynamic by Keycloak if the @Resource annotation has been defined.

Note: As you can see, all annotations are optional. If you don't define any of them, just a token integrity check via JWKS will be done, and in case of success, the endpoint will be authorized.

Get user data

import { AuthService } from '@edirect/auth';

@Injectable()
export class AnyInjectableClass {

  constructor(
    //...
    private authService: AuthService,
  ) {}

  yourMethod() {
    const user = this.authService.getUser();
    //...
  }
}

Required environment variables

# .{NODE_ENV}.ENV

# AuthService provider settings
AUTH_SERVICE_JWKS=https://auth-service-base-url/oidc/jwks

# Keycloak provider settings
KEYCLOAK_BASE_URL=http://keycloak-base-url
KEYCLOAK_REALM=keycloak_realm
KEYCLOAK_CLIENT=your-app-client

# For all providers
HTTP_TIMEOUT=30000 # optional

Multi-Tenancy environment variables

The auth-module supports multi-tenancy in keycloak authentication. If your service receives requests from services in different regions so Europe (stage1-eu, rc-eu, ...) or Asia (stage1-asia, rc-asia, ...) you can configure a specific environment variable for each environment. If the environment variable is not defined, the default environment will be used.

Note: Available from version 9.6.0.

# default
KEYCLOAK_BASE_URL=http://keycloak-base-url
KEYCLOAK_REALM=keycloak_realm
KEYCLOAK_CLIENT=your-app-client

# stage1-eu
KEYCLOAK_STAGE1_EU_BASE_URL=http://keycloak-base-url # KEYCLOAK_{realm_name}_BASE_URL
KEYCLOAK_STAGE1_EU_REALM=stage1-eu # KEYCLOAK_{realm_name}_REALM
KEYCLOAK_STAGE1_EU_CLIENT=your-app-client-in-stage1-eu # KEYCLOAK_{realm_name}_CLIENT

# stage1-as
KEYCLOAK_STAGE1_AS_BASE_URL=http://keycloak-base-url # KEYCLOAK_{realm_name}_BASE_URL
KEYCLOAK_STAGE1_AS_REALM=stage1-as # KEYCLOAK_{realm_name}_REALM
KEYCLOAK_STAGE1_AS_CLIENT=your-app-client-in-stage1-as # KEYCLOAK_{realm_name}_CLIENT

# rc-eu
KEYCLOAK_RC_EU_BASE_URL=http://keycloak-base-url # KEYCLOAK_{realm_name}_BASE_URL
KEYCLOAK_RC_EU_REALM=rc-eu # KEYCLOAK_{realm_name}_REALM
KEYCLOAK_RC_EU_CLIENT=your-app-client-in-rc-eu # KEYCLOAK_{realm_name}_CLIENT

# rc-as
KEYCLOAK_RC_AS_BASE_URL=http://keycloak-base-url # KEYCLOAK_{realm_name}_BASE_URL 
KEYCLOAK_RC_AS_REALM=rc-as # KEYCLOAK_{realm_name}_REALM
KEYCLOAK_RC_AS_CLIENT=your-app-client-in-rc-as # KEYCLOAK_{realm_name}_CLIENT

Important: On receiving an authenticated request, the auth-module will get the realm name in the token metadata. Then based on the default key name, it will create a specific key name for the realm name and try to get its value. If any value is found for the specific key name, it will return it. Otherwise, the value of the default key name will be returned. So, make sure to set all the variables for each environment with no empty values.

Helper function to get process.env variables

If you get each one of these variables in this process.env, you can use the convienient function as shown below.

import { getProcessEnvByRealmKey } from './realm-env-key.functions';

//...
const keycloakBaseUrl = getProcessEnvByRealmKey('KEYCLOAK_BASE_URL', 'stage1-eu');

Note: The first argument should be the default key name. So this function will use the second argument to get the specific key name to the realm name. If any value is found for the specific key name, it will return it. Otherwise, the value of the default key name will be returned. So, make sure to set all the variables for each environment with no empty values.

Use of token exchange

The token exchange solution allows your application to accept external tokens of different IdPs and exchange automatically them for tokens of your IdP. If the access token is of your IdP, the middleware will ignored automatically without affecting your application.

Note: Available from version 9.4.14.

Module settings

import { Module, NestModule, MiddlewareConsumer } from '@nestjs/common';
import {
  AuthMiddleware,
  KeycloakAuthModule,
  KeycloakAuthTokenExchangeMiddleware,
  KeycloakProviderModule,
  OAuth2ParamsInterface,
} from '@edirect/auth';

const keycloakConfig = {
  baseUrl: process.env.KEYCLOAK_BASE_URL,
  wellKnownPath: `/realms/${process.env.KEYCLOAK_REALM}/.well-known/openid-configuration`,
  grantType: 'client_credentials',
  clientId: process.env.KEYCLOAK_CLIENT,
  clientSecret: process.env.KEYCLOAK_CLIENT_SECRET, // name suggestion for environment variable
  scope: process.env.KEYCLOAK_SCOPE, // name suggestion for environment variable
  adminPath: `/admin/realms/${process.env.KEYCLOAK_REALM}`,
} as OAuth2ParamsInterface;

@Module({
  imports: [
    //... same specified above
    KeycloakAuthModule.register(oAuth2Params),
    KeycloakProviderModule.register(oAuth2Params),
  ],
  providers: [
    //... same specified above
  ],
})
export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer): void {
    // --- Attention! Must always precede AuthMiddleware middleware ---
    consumer.apply(KeycloakAuthTokenExchangeMiddleware, AuthMiddleware).forRoutes('cats');
  }
}

Get user data

import { AuthService } from '@edirect/auth';

@Injectable()
export class AnyInjectableClass {

  constructor(
    //...
    private authService: AuthService,
  ) {}

  yourMethod() {
    // User data on external IdP
    const externalUser = this.authService.getExternalUser();
    // User data on your IdP
    const user = this.authService.getUser();
    //...
  }
}

Use to generate internal access tokens for server-to-server communication with other microservices

Allows you to enable at runtime whether the microservice will generate and use access tokens from AuthService or Keycloak in server-to-server communication with another microservice.This feature is highly recommended as it is built to optimize the reuse of one or more access tokens through caching and expiration management.

Note: Available from version 9.2.0.

Module settings

import { Module } from '@nestjs/common';

@Module({
  imports: [
    //... your modules
    AuthModule,
  ],
})
export class AppModule { }

Get token

import { AuthService } from '@edirect/auth';

@Injectable()
export class AnyInjectableClass {

  private keycloakEnable = process.env.KEYCLOAK_ENABLE === 'true';

  constructor(
    //...
    private serverAuthService: ServerAuthService,
  ) {}

  yourMethod() {
    const ob = this.serverAuthService.getToken(
      {
        authService: {
          url: process.env.AUTH_SERVICE_URL, // name suggestion for environment variable
          clientId: process.env.AUTH_SERVICE_CLIENT_ID, // name suggestion for environment variable
          clientSecret: process.env.AUTH_SERVICE_CLIENT_SECRET, // name suggestion for environment variable
          scope: process.env.AUTH_SERVICE_SCOPE, // name suggestion for environment variable
        },
        keycloak: {
          enable: this.keycloakEnable,
          baseUrl: process.env.KEYCLOAK_BASE_URL,
          realm: process.env.KEYCLOAK_REALM,
          clientId: process.env.KEYCLOAK_CLIENT,
          clientSecret: process.env.KEYCLOAK_CLIENT_SECRET, // name suggestion for environment variable
          scope: process.env.KEYCLOAK_SCOPE, // name suggestion for environment variable
        },
      },
      'cache-token-key',
    );
    //...
  }
}

In this code snippet above, we optionally set keycloakEnable as a class property. It will be filled with the contents of the KEYCLOAK_ENABLE environment variable as the default value. We chose to do it this way to demonstrate that we can change it at runtime depending on your business rule. Then we inject an instance of ServerAuthService into the class constructor to use its getToken method in the most suitable place for your business rule. Finally, we highlight the second parameter of the getToken method that we defined with the string cache-token-key. It is optional and should only be used if your microservice requires managing a unique access token for each microservice it communicates with. It represents the unique identifier of the access token cache for its reuse and renewal.

Use of providers

Providers are modules that create an abstraction of the complexity of communicating with supported IAMs through a standard interface. Currently, are supported:

Module settings

  • AuthService
import { Module } from '@nestjs/common';
import { AuthServiceProviderModule } from '@edirect/auth';

const authServiceConfig = {
  baseUrl: process.env.AUTH_SERVICE_URL, // name suggestion for environment variable
  wellKnownPath: '/oidc/.well-known/openid-configuration',
  grantType: 'client_credentials',
  clientId: process.env.AUTH_SERVICE_CLIENT_ID, // name suggestion for environment variable
  clientSecret: process.env.AUTH_SERVICE_CLIENT_SECRET, // name suggestion for environment variable
  scope: process.env.AUTH_SERVICE_SCOPE, // name suggestion for environment variable
  adminPath: '',
} as OAuth2ParamsInterface;

@Module({
  imports: [AuthServiceProviderModule.register(authServiceConfig)]
})
export class AppModule { }
  • Keycloak
import { Module } from '@nestjs/common';
import { KeycloakProviderModule } from '@edirect/auth';

const keycloakConfig = {
  baseUrl: process.env.KEYCLOAK_BASE_URL,
  wellKnownPath: `/realms/${process.env.KEYCLOAK_REALM}/.well-known/openid-configuration`,
  grantType: 'client_credentials',
  clientId: process.env.KEYCLOAK_CLIENT,
  clientSecret: process.env.KEYCLOAK_CLIENT_SECRET, // name suggestion for environment variable
  scope: process.env.KEYCLOAK_SCOPE, // name suggestion for environment variable
  adminPath: `/admin/realms/${process.env.KEYCLOAK_REALM}`,
} as OAuth2ParamsInterface;

@Module({
  imports: [KeycloakProviderModule.register(keycloakConfig)]
})
export class AppModule { }

Provider usage

  • AuthService
import { AuthServiceUserProvider, AuthServiceUser } from '@edirect/auth';
import { Injectable } from '@nestjs/common';

@Injectable()
export class AnyInjectableClass {

  constructor(
    //...
    private readonly userProvider: AuthServiceUserProvider
  ) { }

  createUser(user: AuthServiceUser): Observable<AuthServiceUser> {
    return this.userProvider.create(user);
  }
}
  • Keycloak
import { KeycloakUserProvider, KeycloakUser } from '@edirect/auth';
import { Injectable } from '@nestjs/common';

@Injectable()
export class AnyInjectableClass {
  constructor(
    //...
    private readonly userProvider: KeycloakUserProvider
  ) { }

  createUser(user: KeycloakUser): Observable<KeycloakUser> {
    return this.userProvider.create(user);
  }
}

Sample Testing

  1. Start Keycloak: docker-compose -f .\keycloak\docker-compose.yml up -d;
  2. Start Sample project case 1 as in the documentation;
  3. Start Sample project case 2 as in the documentation;
  4. Run the command curl --location --request GET 'http://0.0.0.0:3000/case1/external-to-internal' \ --header 'Authorization: Bearer eyJhbG...Md2wDw' to simulate a request to your microservice and return access token information;
// Expected output for item 4
{
    "case": 1,
    "user": {
        "clientId": "5c6ea2b88b02943de7fab31a",
        "sub": "LWJmBVI0-OyqQuiMz7Y0w",
        "accountId": "LWJmBVI0-OyqQuiMz7Y0w",
        "username": "caminos#[email protected]",
        "roles": [
            "ROLE_BROKER",
            "ROLE_USER"
        ],
        "permissions": [
            "QUOTE.ENTITY.READ",
            "QUOTE.ENTITY.CREATE",
            "QUOTE.ENTITY.UPDATE",
            "QUOTE.ENTITY.DELETE",
            "QUOTE.ENTITY.TRANSFER",
            "ENTITY.READ",
            "ENTITY.CREATE",
            "ENTITY.UPDATE",
            "ENTITY.DELETE",
            "QUOTE.ENTITY.SHOW_COMMISSION",
            "QUOTE.READ_ONLY_OWN",
            "QUOTE.CREATE",
            "STORAGE.READ",
            "STORAGE.CREATE",
            "STORAGE.UPDATE",
            "STORAGE.DELETE"
        ],
        "status": "active",
        "entityUser": {
            "_id": "6287815a20e555002044c1ed",
            "deletedAt": null,
            "bankCode": "",
            "branchCode": "",
            "sso": false,
            "accountId": "LWJmBVI0-OyqQuiMz7Y0w",
            "entityId": "caminos",
            "firstName": "Adailson",
            "lastName": "Moreiraa",
            "email": "[email protected]",
            "referenceCode": 0,
            "entityUserId": "om11a-ox6GsrVCuMKAC0T",
            "createdAt": "2022-05-20T11:54:03.249Z",
            "updatedAt": "2022-06-23T09:53:55.207Z",
            "__v": 0,
            "vatNumber": "ES01234567A",
            "phone": "972367087",
            "hashUser": "62DEDE7FA9483FB24CFCA47D2686D151",
            "entities": [
                "caminosalmagro42",
                "caminosalmagro8",
                "caminosbcbarcelona",
                "caminosbcmadridalmagro42",
                "caminosbcmadridalmagro8",
                "far"
            ]
        },
        "jti": "ZX_5nXxPE_eSpd2HB3IRD",
        "iat": 1683900016,
        "exp": 1683986416,
        "scope": "openid accountId username roles permissions status entityUser",
        "iss": "http://0.0.0.0:9090",
        "aud": "5c6ea2b88b02943de7fab31a"
    },
    "rolesToCheck": [
        "ROLE_USER"
    ],
    "roleToCheck": "ROLE_USER",
    "permissionsToCheck": [
        "ENTITY.READ",
        "ENTITY.READ_ONLY_OWN"
    ],
    "permissionToCheck": "ENTITY.READ",
    "auth": {
        "getToken": "eyJ...xmA",
        "getUser": {
            "clientId": "5c6ea2b88b02943de7fab31a",
            "sub": "LWJmBVI0-OyqQuiMz7Y0w",
            "accountId": "LWJmBVI0-OyqQuiMz7Y0w",
            "username": "caminos#[email protected]",
            "roles": [
                "ROLE_BROKER",
                "ROLE_USER"
            ],
            "permissions": [
                "QUOTE.ENTITY.READ",
                "QUOTE.ENTITY.CREATE",
                "QUOTE.ENTITY.UPDATE",
                "QUOTE.ENTITY.DELETE",
                "QUOTE.ENTITY.TRANSFER",
                "ENTITY.READ",
                "ENTITY.CREATE",
                "ENTITY.UPDATE",
                "ENTITY.DELETE",
                "QUOTE.ENTITY.SHOW_COMMISSION",
                "QUOTE.READ_ONLY_OWN",
                "QUOTE.CREATE",
                "STORAGE.READ",
                "STORAGE.CREATE",
                "STORAGE.UPDATE",
                "STORAGE.DELETE"
            ],
            "status": "active",
            "entityUser": {
                "_id": "6287815a20e555002044c1ed",
                "deletedAt": null,
                "bankCode": "",
                "branchCode": "",
                "sso": false,
                "accountId": "LWJmBVI0-OyqQuiMz7Y0w",
                "entityId": "caminos",
                "firstName": "Adailson",
                "lastName": "Moreiraa",
                "email": "[email protected]",
                "referenceCode": 0,
                "entityUserId": "om11a-ox6GsrVCuMKAC0T",
                "createdAt": "2022-05-20T11:54:03.249Z",
                "updatedAt": "2022-06-23T09:53:55.207Z",
                "__v": 0,
                "vatNumber": "ES01234567A",
                "phone": "972367087",
                "hashUser": "62DEDE7FA9483FB24CFCA47D2686D151",
                "entities": [
                    "caminosalmagro42",
                    "caminosalmagro8",
                    "caminosbcbarcelona",
                    "caminosbcmadridalmagro42",
                    "caminosbcmadridalmagro8",
                    "far"
                ]
            },
            "jti": "ZX_5nXxPE_eSpd2HB3IRD",
            "iat": 1683900016,
            "exp": 1683986416,
            "scope": "openid accountId username roles permissions status entityUser",
            "iss": "http://0.0.0.0:9090",
            "aud": "5c6ea2b88b02943de7fab31a"
        },
        "getEntityUser": {
            "_id": "6287815a20e555002044c1ed",
            "deletedAt": null,
            "bankCode": "",
            "branchCode": "",
            "sso": false,
            "accountId": "LWJmBVI0-OyqQuiMz7Y0w",
            "entityId": "caminos",
            "firstName": "Adailson",
            "lastName": "Moreiraa",
            "email": "[email protected]",
            "referenceCode": 0,
            "entityUserId": "om11a-ox6GsrVCuMKAC0T",
            "createdAt": "2022-05-20T11:54:03.249Z",
            "updatedAt": "2022-06-23T09:53:55.207Z",
            "__v": 0,
            "vatNumber": "ES01234567A",
            "phone": "972367087",
            "hashUser": "62DEDE7FA9483FB24CFCA47D2686D151",
            "entities": [
                "caminosalmagro42",
                "caminosalmagro8",
                "caminosbcbarcelona",
                "caminosbcmadridalmagro42",
                "caminosbcmadridalmagro8",
                "far"
            ]
        },
        "hasRoles": true,
        "hasRole": true,
        "hasPermissions": false,
        "hasPermission": true,
        "isAdmin": false,
        "isUser": true,
        "isService": false,
        "isCrm": false,
        "isBroker": true,
        "isAgentLicensed": false,
        "isAgentUnlicensed": false,
        "getTokenProvider": "authservice"
    }
}
  1. Run the command curl --location --request GET 'http://0.0.0.0:3000/case1/external-to-internal' \ --header 'Authorization: Bearer eyJhbG...Md2wDw' to simulate a request to your microservice, where internally there is communication with another microservice and return the internal access token information.
// Expected output for item 4
{
    "case": 2,
    "user": {
        "clientId": "5c6ea2b88b02943de7fab31a",
        "accountId": "aYoqi4OK5Gb72Nxqg8QD6",
        "username": "edirect#[email protected]",
        "roles": [
            "ROLE_SERVICE"
        ],
        "permissions": [
            "QUOTE.READ",
            "QUOTE.CREATE",
            "QUOTE.UPDATE",
            "QUOTE.DELETE",
            "ENTITY.READ",
            "ENTITY.CREATE",
            "ENTITY.UPDATE",
            "ENTITY.DELETE",
            "STORAGE.READ",
            "STORAGE.CREATE",
            "STORAGE.UPDATE",
            "STORAGE.DELETE"
        ],
        "status": "active",
        "jti": "31jETjs3N9MoF1-PJ6ugA",
        "iat": 1683900332,
        "exp": 1683900932,
        "scope": "openid accountId username roles permissions status entityUser",
        "iss": "http://0.0.0.0:9090",
        "aud": "5c6ea2b88b02943de7fab31a"
    },
    "rolesToCheck": [
        "ROLE_USER"
    ],
    "roleToCheck": "ROLE_USER",
    "permissionsToCheck": [
        "ENTITY.READ",
        "ENTITY.READ_ONLY_OWN"
    ],
    "permissionToCheck": "ENTITY.READ",
    "auth": {
        "getToken": "eyJ...19w",
        "getUser": {
            "clientId": "5c6ea2b88b02943de7fab31a",
            "accountId": "aYoqi4OK5Gb72Nxqg8QD6",
            "username": "edirect#[email protected]",
            "roles": [
                "ROLE_SERVICE"
            ],
            "permissions": [
                "QUOTE.READ",
                "QUOTE.CREATE",
                "QUOTE.UPDATE",
                "QUOTE.DELETE",
                "ENTITY.READ",
                "ENTITY.CREATE",
                "ENTITY.UPDATE",
                "ENTITY.DELETE",
                "STORAGE.READ",
                "STORAGE.CREATE",
                "STORAGE.UPDATE",
                "STORAGE.DELETE"
            ],
            "status": "active",
            "jti": "31jETjs3N9MoF1-PJ6ugA",
            "iat": 1683900332,
            "exp": 1683900932,
            "scope": "openid accountId username roles permissions status entityUser",
            "iss": "http://0.0.0.0:9090",
            "aud": "5c6ea2b88b02943de7fab31a"
        },
        "hasRoles": false,
        "hasRole": false,
        "hasPermissions": false,
        "hasPermission": true,
        "isAdmin": false,
        "isUser": false,
        "isService": true,
        "isCrm": false,
        "isBroker": false,
        "isAgentLicensed": false,
        "isAgentUnlicensed": false,
        "getTokenProvider": "authservice"
    }
}

Note: For this to work, your application must start its bootstrap as nest-app.

Problems

The cache-manager version

This library underwent a change from version 4 to 5 that will significantly change its behavior (more details in this link). We, therefore, recommend that it be updated to version 5 in your project.