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.

Last week I wrote about how to use a MultiSamlStrategy to support multiple apps. This week I'm going to show you how you can define a metadata route for each of your applications. The metadata is used to communicate app changes between the IDP and the the Service Provider (your app).

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.

It isn't too hard to make this happen thanks to our underlying Passport-SAML library.

First, remember our config:

view plain print about
1"appConfigs": {
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 },

It is, basically, a set where each key contains one app's config. The appName is going to be used in the URL.

We'll use a parameter in the metadata route, and then use that parameter to search through the config to find the app name. Start with the route definition:

view plain print about
1router.get('/metadata/:appName', (req: Request, res: Response, next: NextFunction) => {

Now get the app Config:

view plain print about
1const appConfigKeys: string[] = Object.keys(appConfigs);
2 let appConfig: AppConfig;
3 appConfigKeys.forEach((appConfigKey) =>
4 if (APP_CONFIGS[appConfigKey].appName === req.params.appName) {
5 appConfig = appConfigs[appConfigKey];
6 }
7 });

Check to make sure the app Config is defined, and if not send back an error:

view plain print about
1if (!appConfig) {
2 res.status(403);
3 next(`The app ${req.params.appName} is not defined`);
4 }

Now, set the relayState based on the appConfig value. Our MultiSamlStrategoy requires that this be set:

view plain print about
1req.query.RelayState = appConfig.loginCallBackUrl;

And finally, send back a valid status of 200 along with the metadata:

view plain print about
1res.status(200).send(multiSamlStrategy.generateServiceProviderMetadata(req, null, null,
2 function (err, data) {
3 if (err) {
4 console.log(`metadata generation error for ${req.params.appname}`, err);
5 return next();
6 }
7 res.set('Content-Type', 'text/xml');
8 res.send(data);
9 }));

All the complexity of this is architected away by the library. Just load 'myapp.com/metadata/app1' or 'myapp.com/metadata/app1' to get the metadata.

I was pretty excited the first time I got this working. It really cleaned up the underlying code base I was working on.