import { Injectable, Logger } from '@nestjs/common'; import { PassportStrategy } from '@nestjs/passport'; import { ExtractJwt, Strategy } from 'passport-jwt'; import * as jwksRsa from 'jwks-rsa'; import { ConfigService } from '@nestjs/config'; @Injectable() export class KeycloakJwtStrategy extends PassportStrategy(Strategy, 'jwt') { private readonly logger = new Logger(KeycloakJwtStrategy.name); constructor(private configService: ConfigService) { const jwksUri = configService.get('KEYCLOAK_JWKS_URI'); const issuer = configService.get('KEYCLOAK_ISSUER'); if (!jwksUri || !issuer) { throw new Error('Missing Keycloak configuration (KEYCLOAK_JWKS_URI / KEYCLOAK_ISSUER)'); } super({ jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), secretOrKeyProvider: jwksRsa.passportJwtSecret({ cache: true, rateLimit: true, jwksRequestsPerMinute: 5, jwksUri, }), issuer, algorithms: ['RS256'], }); } async validate(payload: any) { // Récupération des rôles du realm const realmRoles: string[] = payload.realm_access?.roles || []; // Récupération des rôles du client dans resource_access const clientId = payload.client_id; const resourceRoles: string[] = payload.resource_access?.[clientId]?.roles || []; // Fusion et suppression des doublons const roles = Array.from(new Set([...realmRoles, ...resourceRoles])); this.logger.verbose(`User ${payload.preferred_username} roles: ${JSON.stringify(roles)}`); return { sub: payload.sub, preferred_username: payload.preferred_username, email: payload.email, client_id: clientId, roles, realmRoles, resourceRoles, }; } }