I wrote about setting up a Passport SAML Config to implement integration with an SSO provider that supports SAML, and how to create a metadata page to communicate changes to your IDP.
This week I'm going to show you how to set up a MultiSamlStrategy.
For background, the app uses ExpressJS a NodeJS web server, Passport, a security framework, and Passport SAML a SAML Plugin to Passport; all running on top of a NodeJS server.
As apps often due, ours grew. And before you knew it our Auth microservice was supporting multiple applications. We could have created multiple named SamlConfigs and then done something in the login route to determine which app to use. It would work, but there is a much cleaner way.
Let's start by creating an app config.
2 "app1.com": {
3 "appName": "app1",
4 "loginCallBackUrl": "myAuthServer/login/callback"
5 "samlIssuer": "app1.com"
6 },
7 "app2.com": {
8 "appName": "app2",
9 "loginCallBackUrl": "myAuthServer/login/callback"
10 "samlIssuer": "app2.com"
11 },
12}
I created an object where each key represents an app's domain URL. We'll use this config, along with the relaystate to create our SamlConfig in a MutliSamlStrategy. A relaystate is just a redirect URL that is part of the SAMLRequest, and your server uses it to redirect from the auth service back to your app.
The MultiSamlStrategy requires a function to determine the config for the request:
2 return {
3 callbackUrl: config.loginCallBackUrl,
4 entryPoint: ENTRYPOINT_URL,
5 identifierFormat: IDENTIFIER_FORMAT,
6 issuer: config.samlIssuer,
7 cert: fs.readFileSync(`${__dirname}/../../certs/sso_token_signing_cert.pem`, 'utf8')
8 };
9}
Now, create the mutliSamlStrategy:
2 getSamlOptions(req, done) {
3 const relayState = UrlUtil.getHostName(req.query.RelayState ? req.query.RelayState : req.body.RelayState);
4 done(null, createSamlConfig(appConfigs[relayState]));
5 },
6 },
7 (profile: Profile, done: VerifiedCallback) => {
8 return done(null, profile, null);
9 }
10);
The first thing this does is introspect the relay state variable. On the first call to the login service, this will be part of the query string. On the callback from the login service, this will be part of the request body. But, either way we need to get the host name from the relay state, because that is the key to our config. We use that to get our appConfig values for the domain in question and use that create a SamlConfig.
Every time we try to use the SAML strategy, it will run the function and send back a properly configured object strictly for the app in question.
Now tell passport to use this strategy:
All good. I gave the MultiSamlStrategy a name, which helps if we have more than one. Now we just need a login route and a callback route. The login route might be like this:
2 (req: Request, res: Response, next: NextFunction) => {
3 passport.authenticate('myMultiSamlStrategy') (req, res, next);
4 }
5);
The user is going to come to the app, hit the login route, redirect to the IDP which will handle authentication, and redirect to your app's callback route. Here is a sample callback:
2 (req: Request, res: Response, next: NextFunction) => {
3 passport.authenticate('myMultiSamlStrategy')(req, res, next);
4 },
5 (req: Request, res: Response) => {
6 // do something to process that result, but beyond the scope of this article
7 if (req.body.RelayState) {
8 res.redirect(req.body.RelayState);
9 } else {
10 res.redirect('/');
11 }
12 }
13);
The callback just uses our passport Multi SAML Strategy to process the results. Then it does something to validate the results. It is up to you what that it is. Then it redirects back to the main app. Presumably with a token, cookie, or some other manner. Really the focus of this was how to set up the SAML Config for a single app.
Next week I'm going to tackle how to set up metadata for Multiple SAML Hosts.