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 🙏

© 2026 – Pkg Stats / Ryan Hefner

@pr2core/librairy-pr2core

v1.0.0

Published

Module de sécurité OIDC/Keycloak pour NestJS — équivalent TypeScript d'un module de sécurité Java

Downloads

13

Readme

librairy-pr2core-mindef

Module de securite OIDC/Keycloak pour NestJS. Equivalent TypeScript du module Java Spring Boot module-security-mindef.

Fournit : authentification OIDC via Keycloak, gestion de session serveur, guards bases sur les roles/autorisations en base locale, logging OAuth2 filtre.


Prerequis

  • Node.js >= 18
  • NestJS >= 9
  • TypeORM >= 0.3
  • Keycloak avec un realm et un client OIDC configure

Installation

npm install @pr2core/librairy-pr2core

Peer dependencies

La librairie declare ces peer dependencies (a installer dans votre projet) :

npm install @nestjs/common @nestjs/core @nestjs/passport @nestjs/typeorm passport typeorm

Dependencies additionnelles necessaires

npm install express-session passport-openidconnect
npm install -D @types/express-session @types/passport

Configuration complete

Etape 1 : Creer les entites TypeORM

La librairie s'appuie sur 3 interfaces que vos entites doivent implementer. Les roles et autorisations sont geres en base locale, pas dans Keycloak.

identifiant.entity.ts — Utilisateur local (lie au preferred_username Keycloak)

import { Entity, PrimaryGeneratedColumn, Column, ManyToMany, JoinTable } from 'typeorm';
import { IdentifiantInterface } from '@pr2core/librairy-pr2core';
import { Profil } from './profil.entity';

@Entity('identifiant')
export class Identifiant implements IdentifiantInterface {
  @PrimaryGeneratedColumn()
  id: number;

  @Column({ unique: true })
  login: string; // DOIT correspondre au preferred_username Keycloak

  @Column({ default: true })
  actif: boolean;

  @Column({ default: false })
  verrouille: boolean;

  @ManyToMany(() => Profil, { eager: true })
  @JoinTable({
    name: 'identifiant_profil',
    joinColumn: { name: 'identifiant_id' },
    inverseJoinColumn: { name: 'profil_id' },
  })
  profils: Profil[];
}

profil.entity.ts — Role utilisateur

import { Entity, PrimaryGeneratedColumn, Column, ManyToMany, JoinTable } from 'typeorm';
import { ProfilInterface } from '@pr2core/librairy-pr2core';
import { Autorisation } from './autorisation.entity';

@Entity('profil')
export class Profil implements ProfilInterface {
  @PrimaryGeneratedColumn()
  id: number;

  @Column({ unique: true })
  nom: string; // Ex: 'ADMIN', 'USER'

  @ManyToMany(() => Autorisation, { eager: true })
  @JoinTable({
    name: 'profil_autorisation',
    joinColumn: { name: 'profil_id' },
    inverseJoinColumn: { name: 'autorisation_id' },
  })
  autorisations: Autorisation[];
}

autorisation.entity.ts — Permission fine

import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';
import { AutorisationInterface } from '@pr2core/librairy-pr2core';

@Entity('autorisation')
export class Autorisation implements AutorisationInterface {
  @PrimaryGeneratedColumn()
  id: number;

  @Column({ unique: true })
  code: string; // Ex: 'READ_USERS', 'WRITE_DOCUMENTS'

  @Column()
  libelle: string;
}

Schema de la base :

identifiant (id, login, actif, verrouille)
    └── identifiant_profil (identifiant_id, profil_id)
            └── profil (id, nom)
                    └── profil_autorisation (profil_id, autorisation_id)
                            └── autorisation (id, code, libelle)

Etape 2 : Implementer le repository d'identifiants

// identifiant.repository.ts
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { IdentifiantRepositoryInterface } from '@pr2core/librairy-pr2core';
import { Identifiant } from './entities/identifiant.entity';

@Injectable()
export class IdentifiantRepository implements IdentifiantRepositoryInterface {
  constructor(
    @InjectRepository(Identifiant)
    private readonly repo: Repository<Identifiant>,
  ) {}

  async findByLogin(login: string) {
    return this.repo.findOne({
      where: { login },
      relations: ['profils', 'profils.autorisations'],
    });
  }
}

Etape 3 : Rendre les repositories TypeORM accessibles globalement

Important : Le module de securite instancie votre IdentifiantRepository via useClass. Pour que l'injection @InjectRepository(Identifiant) fonctionne dans ce contexte, les repositories TypeORM doivent etre disponibles globalement.

Creez un module global qui exporte TypeORM :

// database-entities.module.ts
import { Global, Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { Identifiant } from './entities/identifiant.entity';
import { Profil } from './entities/profil.entity';
import { Autorisation } from './entities/autorisation.entity';

@Global()
@Module({
  imports: [TypeOrmModule.forFeature([Identifiant, Profil, Autorisation])],
  exports: [TypeOrmModule],
})
export class DatabaseEntitiesModule {}

Etape 4 : Importer le module de securite

// app.module.ts
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { TypeOrmModule } from '@nestjs/typeorm';
import { SecurityPr2coreMindefModule } from '@pr2core/librairy-pr2core';
import { DatabaseEntitiesModule } from './database-entities.module';
import { IdentifiantRepository } from './repositories/identifiant.repository';
import { Identifiant } from './entities/identifiant.entity';
import { Profil } from './entities/profil.entity';
import { Autorisation } from './entities/autorisation.entity';

@Module({
  imports: [
    // Charger le .env AVANT le module de securite
    ConfigModule.forRoot({ isGlobal: true }),

    TypeOrmModule.forRoot({
      type: 'postgres', // ou 'better-sqlite3', 'mysql', etc.
      // ... votre config DB
      entities: [Identifiant, Profil, Autorisation],
      synchronize: false, // true uniquement en dev
    }),

    // OBLIGATOIRE : rendre les repositories TypeORM globaux
    DatabaseEntitiesModule,

    // Module de securite
    SecurityPr2coreMindefModule.forRoot({
      config: {
        keycloak: {
          clientId: process.env.KEYCLOAK_CLIENT_ID!,
          clientSecret: process.env.KEYCLOAK_CLIENT_SECRET!,
          serverUrl: process.env.KEYCLOAK_SERVER_URL!,
          realm: process.env.KEYCLOAK_REALM!,
          registrationId: 'keycloak',
          scope: 'openid',
          userNameAttribute: 'preferred_username',
        },
        backend: {
          url: process.env.BACKEND_URL!,       // Ex: http://localhost:3000
          redirectUri: '/api/auth/callback',    // DOIT correspondre a la route du controller
        },
        frontend: {
          url: process.env.FRONTEND_URL!,      // Ex: http://localhost:4200
        },
        session: {
          secret: process.env.SESSION_SECRET!,
          maxAge: 86400000, // 24h
        },
      },
      identifiantRepository: IdentifiantRepository,
    }),
  ],
})
export class AppModule {}

Attention : ConfigModule.forRoot() doit etre importe avant SecurityPr2coreMindefModule.forRoot() dans le tableau imports pour que les process.env.* soient disponibles.

Etape 5 : Configurer la session Express dans main.ts

import { NestFactory } from '@nestjs/core';
import * as session from 'express-session';
import * as passport from 'passport';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);

  // Session Express (necessaire pour Passport avec sessions)
  app.use(
    session({
      secret: process.env.SESSION_SECRET || 'changeme',
      resave: false,
      saveUninitialized: false,
      cookie: {
        maxAge: 86400000,   // 24h
        httpOnly: true,
        secure: false,      // Mettre true en production avec HTTPS
        sameSite: 'lax',
      },
    }),
  );

  // Initialisation Passport
  app.use(passport.initialize());
  app.use(passport.session());

  // CORS si frontend sur un port/domaine different
  app.enableCors({
    origin: process.env.FRONTEND_URL,
    credentials: true,  // Necessaire pour envoyer les cookies de session
  });

  await app.listen(3000);
}
bootstrap();

Note : cookie.secure doit etre false en developpement (HTTP). En production (HTTPS), passez-le a true.


Variables d'environnement

# Keycloak
KEYCLOAK_SERVER_URL=https://keycloak.example.com
KEYCLOAK_REALM=mon-realm
KEYCLOAK_CLIENT_ID=mon-client
KEYCLOAK_CLIENT_SECRET=secret-du-client

# URLs
BACKEND_URL=http://localhost:3000
FRONTEND_URL=http://localhost:4200

# Session
SESSION_SECRET=un-secret-tres-long-et-aleatoire

Configuration Keycloak

1. Client OIDC

| Parametre | Valeur | |---|---| | Client ID | mon-client | | Client type | OpenID Connect | | Client authentication | ON (confidential) | | Standard flow | ON (Authorization Code Flow) | | Valid redirect URIs | http://localhost:3000/* (dev) | | Valid post logout redirect URIs | http://localhost:4200/* (dev) | | Web origins | http://localhost:3000, http://localhost:4200 |

Le Client Secret se trouve dans l'onglet Credentials du client.

2. Utilisateurs

Creer les utilisateurs dans le realm Keycloak. Le champ Username doit correspondre exactement au champ login de la table identifiant en base locale.

La librairie utilise le claim preferred_username (configurable via userNameAttribute) pour faire la correspondance avec findByLogin().

Les roles sont geres en base locale, pas dans Keycloak. Keycloak sert uniquement a authentifier l'utilisateur.


Utilisation dans les controleurs

import { Controller, Get } from '@nestjs/common';
import { Public, Roles, CurrentUser, CustomOidcUser } from '@pr2core/librairy-pr2core';

@Controller('api/documents')
export class DocumentsController {
  // Route publique — accessible sans authentification
  @Public()
  @Get('public')
  publicEndpoint() {
    return { message: 'accessible sans authentification' };
  }

  // Route protegee — authentification requise
  @Get('mine')
  myDocuments(@CurrentUser() user: CustomOidcUser) {
    return {
      login: user.login,        // string
      roles: user.roles,        // string[] (noms des profils)
      authorities: user.authorities, // string[] (codes des autorisations)
    };
  }

  // Route avec role requis
  @Roles('ADMIN')
  @Get('admin')
  adminOnly(@CurrentUser() user: CustomOidcUser) {
    return { message: `Bienvenue ${user.login}` };
  }
}

Decorateurs disponibles

| Decorateur | Description | |---|---| | @Public() | Marque la route comme accessible sans authentification | | @Roles('ADMIN', 'USER') | Exige un des roles (noms de profils en base) | | @CurrentUser() | Injecte l'objet CustomOidcUser depuis la session |

Objet CustomOidcUser

| Propriete / Methode | Type | Description | |---|---|---| | login | string | Login de l'utilisateur | | roles | string[] | Noms des profils en base | | authorities | string[] | Codes des autorisations en base | | hasRole(role) | boolean | Verifie si l'utilisateur a le role | | hasAuthority(code) | boolean | Verifie si l'utilisateur a l'autorisation | | identifiant | IdentifiantInterface | Entite complete en base | | oidcClaims | Record<string, unknown> | Claims OIDC bruts du provider |


Endpoints fournis

Tous les endpoints sont montes automatiquement par le module :

| Methode | Route | Auth | Description | |---------|-------|------|-------------| | GET | /api/auth/login | Non | Redirige vers Keycloak | | GET | /api/auth/callback | Non | Callback OAuth2 (gere par Passport) | | GET | /api/auth/me | Oui | Retourne l'utilisateur connecte | | GET | /api/auth/check | Oui | Statut d'authentification | | POST | /api/auth/logout | Non | Deconnexion (session + Keycloak RP-Initiated Logout) |

Flow d'authentification

Navigateur           Backend NestJS           Keycloak
    |                     |                      |
    |-- GET /api/auth/login -->                   |
    |                     |-- 302 redirect ------>|
    |                     |                      |
    |<---------- Page de login Keycloak ---------|
    |-- credentials ----->|                      |
    |                     |<-- 302 + auth code --|
    |                     |                      |
    |<-- GET /api/auth/callback (code) -->        |
    |                     |-- exchange code ----->|
    |                     |<-- tokens -----------|
    |                     |                      |
    |                     |-- findByLogin(preferred_username)
    |                     |-- session.save()      |
    |<-- 302 redirect vers FRONTEND_URL           |
    |                     |                      |
    |-- GET /api/auth/me (cookie session) -->     |
    |<-- 200 { login, roles, authorities } --     |

Regles de securite personnalisees

Pour ajouter des regles de securite propres a votre application :

import { Injectable } from '@nestjs/common';
import { SecurityRulesSupplier, SecurityRule } from '@pr2core/librairy-pr2core';

@Injectable()
export class AppSecurityRules implements SecurityRulesSupplier {
  getRules(): SecurityRule[] {
    return [
      { path: '/api/public/**', access: 'permitAll' },
      { path: '/api/internal/**', access: 'authenticated' },
    ];
  }
}

Puis passez-les au module :

SecurityPr2coreMindefModule.forRoot({
  config: { /* ... */ },
  identifiantRepository: IdentifiantRepository,
  additionalRulesSuppliers: [AppSecurityRules],
})

Correspondance Java -> NestJS

| Java (Spring) | NestJS (librairy-pr2core-mindef) | |---|---| | SecurityConfiguration | SecurityPr2coreMindefModule.forRoot() | | ClientRegistrationConfig | SecurityPr2coreMindefConfig.keycloak | | CustomOidcUserService | CustomOidcUserService | | CustomOidcUser (record) | CustomOidcUser (class) | | OAuth2LoggingFilter | OAuth2LoggingMiddleware | | OAuth2LoginSuccessHandler | AuthController.callback() | | OidcPr2coreSecurityRules | OidcPr2coreMindefSecurityRules | | TokenRefreshController | AuthController | | @PreAuthorize("hasRole()") | @Roles() | | @AuthenticationPrincipal | @CurrentUser() | | .permitAll() | @Public() | | SecurityRulesSupplier | SecurityRulesSupplier | | application.yml (security.*) | SecurityPr2coreMindefConfig |


API exportee

// Module principal
export { SecurityPr2coreMindefModule, SecurityPr2coreMindefModuleOptions } from '...';

// Configuration
export { SecurityPr2coreMindefConfig, SECURITY_PR2CORE_MINDEF_CONFIG, buildKeycloakUrls } from '...';

// Interfaces (a implementer par vos entites)
export { IdentifiantInterface } from '...';
export { ProfilInterface } from '...';
export { AutorisationInterface } from '...';
export { IdentifiantRepositoryInterface, IDENTIFIANT_REPOSITORY } from '...';
export { SecurityRule, SecurityRulesSupplier, SECURITY_RULES_SUPPLIERS } from '...';

// Modele utilisateur
export { CustomOidcUser } from '...';

// Services
export { CustomOidcUserService } from '...';

// Guards
export { OidcAuthGuard } from '...';
export { OidcCallbackGuard } from '...';
export { RolesGuard } from '...';

// Decorateurs
export { Public, IS_PUBLIC_KEY } from '...';
export { Roles, ROLES_KEY } from '...';
export { CurrentUser } from '...';

// Regles de securite
export { OidcPr2coreMindefSecurityRules } from '...';

// Middleware
export { OAuth2LoggingMiddleware } from '...';

Tests

npm test           # 93 tests, 100% coverage
npm run test:cov   # Avec rapport de couverture