Files
scouthub/scouthub-eventi-be/src/auth/verify-service-token.middleware.ts
T
2026-07-25 11:59:25 +02:00

53 lines
2.0 KiB
TypeScript

import { Request, Response, NextFunction } from 'express';
import jwt, { JwtPayload } from 'jsonwebtoken';
import { jwksClientInstance } from './jwks-client';
import { extractBearerToken } from './extract-bearer-token';
import { env } from '../config/env';
import { ServiceAuthContext } from './auth.types';
interface ServiceTokenPayload extends JwtPayload {
// "azp" (authorized party) e' il client_id del client che ha ottenuto il token
// via client credentials grant: e' il modo standard per riconoscere quale
// servizio sta chiamando in una chiamata machine-to-machine.
azp?: string;
}
// Middleware da usare al posto di (non insieme a) verifyToken sulle route
// machine-to-machine, ad es. POST /eventi/:id/risorse chiamata da altri backend
// (scouthub-attivita-be, scouthub-magazzino-be, ...) con le proprie credenziali
// di servizio. Il client chiamante deve essere elencato in
// KEYCLOAK_AUTHORIZED_SERVICE_CLIENTS.
export async function verifyServiceToken(req: Request, res: Response, next: NextFunction): Promise<void> {
const token = extractBearerToken(req);
if (!token) {
res.status(401).json({ message: 'Token mancante' });
return;
}
try {
const decoded = jwt.decode(token, { complete: true });
if (!decoded || !decoded.header.kid) {
throw new Error("Header del token privo di 'kid'");
}
const signingKey = await jwksClientInstance.getSigningKey(decoded.header.kid);
const payload = jwt.verify(token, signingKey.getPublicKey(), { algorithms: ['RS256'] });
if (typeof payload === 'string') {
throw new Error('Payload del token non valido');
}
const clientId = (payload as ServiceTokenPayload).azp;
if (!clientId || !env.keycloak.authorizedServiceClients.includes(clientId)) {
res.status(403).json({ message: 'Client di servizio non autorizzato' });
return;
}
const service: ServiceAuthContext = { clientId };
req.service = service;
next();
} catch {
res.status(401).json({ message: 'Token non valido' });
}
}