Files
clipperia-api/src/auth/keycloak.strategy.ts
2025-09-13 16:02:52 -03:00

63 lines
1.6 KiB
TypeScript

import { Injectable, UnauthorizedException } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { ExtractJwt, Strategy } from 'passport-jwt';
import * as jwksRsa from 'jwks-rsa';
export type JwtAudience = string | string[] | undefined;
export interface JwtRealmAccess {
roles: string[];
}
export interface JwtResourceAccessEntry {
roles: string[];
}
export type JwtResourceAccess =
| Record<string, JwtResourceAccessEntry>
| undefined;
export interface JwtPayload {
sub: string;
email?: string;
preferred_username?: string;
given_name?: string;
family_name?: string;
scope?: string;
realm_access?: JwtRealmAccess;
resource_access?: JwtResourceAccess;
iat: number;
exp: number;
iss: string;
aud?: JwtAudience;
[claim: string]: unknown;
}
@Injectable()
export class KeycloakJwtStrategy extends PassportStrategy(Strategy, 'jwt') {
constructor() {
const baseUrl =
process.env.NODE_ENV === 'production'
? 'http://keycloak:8080'
: 'https://auth.clipperia.com.br';
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
secretOrKeyProvider: jwksRsa.passportJwtSecret({
cache: true,
rateLimit: true,
jwksRequestsPerMinute: 5,
jwksUri: `${baseUrl}/realms/clipperia/protocol/openid-connect/certs`,
}),
algorithms: ['RS256'],
issuer: `${baseUrl}/realms/clipperia`,
ignoreExpiration: false,
});
}
validate(payload: JwtPayload): JwtPayload {
if (payload.exp < Date.now() / 1000) {
throw new UnauthorizedException('Token expirado');
}
return payload;
}
}