Gérer votre connexion SSO SAML avec Nestjs et passport-saml

La mise en place de solution SSO pour gérer votre authentification peut être rapidement abstraite. La méthode SAML n'est pas la solution la plus simple à mettre en place. Heureusement, certaines librairies sont là pour nous faciliter la tâche.

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 

Un projet, une idée ? Contactez-moi !

Logo Galibert Thomas Développement

Développeur full-stack et DevOps freelance sur Toulouse avec cinq ans d'expérience. J'ai travaillé sur divers projets en tant que consultant indépendant, apportant mes compétences en développement logiciel et en gestion des opérations pour aider mes clients à atteindre leurs objectifs.

Vous avez un projet et vous souhaitez vous faire accompagner ? Que ce soit un site web vitrine, une boutique e-commerce, une application web, mobile ou desktop, n'hésitez pas a me contacter. Je trouverez la meilleure solution à votre besoin.

© Thomas GALIBERT - Développeur Fullstack & DevOps - Toulouse - 2023