57 lines
1.7 KiB
TypeScript
57 lines
1.7 KiB
TypeScript
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<string>('KEYCLOAK_JWKS_URI');
|
|
const issuer = configService.get<string>('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,
|
|
};
|
|
}
|
|
}
|