Pré-requis
Dans un premier temps, vous allez devoir récupérer plusieurs packages NPM que nous allons utiliser :
npm install '@node-saml/passport-saml'
Votre API Nestjs devra également utiliser @nestjs/passport
.
Une fois ces pre-requis installé, passons à la création de votre service SAML qui étendra PassportStrategy
de @nestjs/passport
Génération des certificats x509
Le SAML utilise le chiffrement des messages par le biais de certificats. Vous devez donc générer ces certificats et les enregistrer a la racine de votre API.
Je vous conseille d'utiliser cet utilitaire pour les générer : One Login SAML Certificats Generator
La clé publique doit s'appeler cert.pem
et la clé privé key.pem
Créez votre service SAML
Créez un service que vous appelerez SamlService
et pensez à le rajouter dans les providers du ou des modules dans lequel il sera utilisé.
import { Injectable } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { Strategy } from '@node-saml/passport-saml'
import * as fs from 'fs'
@Injectable()
export class SamlService extends PassportStrategy(Strategy, 'saml') {
constructor() {
super({
issuer: '<ID PERMETTEANT D'IDENTIFIER VOTRE SERVICE>',
callbackUrl: '<URL CALLBACK DE VOTRE API SUR LEQUEL L'IDP REPOND>',
cert: "<CERTFIFICAT PUBLIC DE VOTRE IDP>",
privateKey: fs.readFileSync('./key.pem', "utf-8"),
decryptionPvk: fs.readFileSync('./key.pem', "utf-8"),
entryPoint: '<URL LOGIN DE VOTRE IDP>',
wantAssertionsSigned: true,
signatureAlgorithm: 'sha256', // ou sha1 ou sha512 selon ce qu'accepte votre idp
digestAlgorithm: 'sha256', // ou sha1 ou sha512 selon ce qu'accepte votre idp
})
}
}
Mise en place des Endpoints
Dans votre controller, vous allez en premier loin rajouter dans le constructeur l'injection de votre service SAML :
constructor(....., private samlStrategy: SamlService) {}
Maintenant, nous allons déclarer un Endpoint permettant d'intercepter la demande de votre client d'initier la connexion SAML :
@Get('/saml/login')
@UseGuards(AuthGuard("saml"))
async samlLogin() {
//this route is handled by passport-saml
return;
}
En utilisant le Guard AuthGuard
fournis par @nestjs/passport
et en lui indiquant la stratégie adopté pour l'autentification, ici "saml", votre service précédement créé va automatiquement prendre en charge la création de la requête SAML et vous rediriger vers votre IDP.
Une fois sur votre IDP, connectez-vous, et si la requête a correctement été signée et votre IDP correctement configurer avec votre service, celui-ci va vous rediriger vers votre autre Endpoint avec les informations relatives à votre utilisateur :
@Post('/saml/validate')
async samlAssertionConsumer(@Req() req, @Res() res: any) {
let buff = Buffer.from(req.body.SAMLResponse, 'base64').toString('utf-8')
// dans buff vous retrouverez vos informations utilisateur au format XML, il ne vous reste plus qu'à les traiter comme vous le souhaitez.
}
Pour finir, il vous sera nécessaire d'avoir un Endpoint supplémentaire, permettant de générer les metadatas de votre configuration SAML. Vous obtiendrez alors un fichier XML avec toutes les informations de configuration SAML que vous pouvez transmettre à votre IDP :
@Get('/saml/metadata.xml')
async getSpMetadata(@Res() res: any) {
let cert = await fs.readFileSync('cert.pem', 'utf-8')
const ret = this.samlStrategy.generateServiceProviderMetadata(cert, cert);
res.type('application/xml');
res.send(ret);
}
Conclusion
Votre service d'authentification SAML est opérationnel avec NestJS. Il ne vous reste plus qu'à adapter les informations de configuration à l'IDP que vous avez choisi.
Si vous rencontrez un problème, un bug ou que vous souhaitez avoir une configuraiton plus "poussée", je vous invite à consulter la documentation de passport-saml