I'm working on a project whose single sign on solution makes use of SAML. SAML stands for Security Assertion Markup Language and it is just an XML Format for two systems to communicate with each other, securely, for authentication purposes. We built a microservice that supports multiple apps. But, for now let's talk about how we got our first one working.

One project I've been working on a NodeJS system with Passport SAML. Passport is an authentication system for JS, and Passport SAML is a plugin to support SAML based Authentication schemes.

Anyway, in the one app world this is how we set things up. First, we created a config object:

view plain print about
1const samlConfig: SamlConfig = {
2 callbackUrl: 'myAuthServer/login/callback',
3 entryPoint: ENTRYPOINT_URL,
4 identifierFormat: IDENTIFIER_FORMAT,
5 issuer: SAML_ISSUER,
6 cert: fs.readFileSync(`${__dirname}/certs/sso_token_signing_cert.pem`, 'utf8')

I have a bunch of placeholder values in that config. Most come from an environment config file:

  • callbackUrl: After a successful login, the Identity Provider needs to redirect back to our app.
  • entryPoint: This is the URL location of the Identity Provider.
  • identifierFormat: This is a value provided by the team managing the single sign on server.
  • issuer: This is some unique value that the Identity Provider users to recognize your app. In our case, it is the URL / Domain.
  • cert: This is a SSL Cert that we created for signing our SAML Requests. The Identity Provider has a copy so they can read our requests, and it can be updated via a shared metadata. I'll create metadata in the next article of this series.

You have a config, create a SamlStrategy using it:

view plain print about
1export const samlStrategy: SamlStrategy =
2 new SamlStrategy(samlConfig, (profile: object, done: VerifiedCallback) =>
3 return done(null, profile, null);
I pass in the our samlConfig to the new SamlStrategy instance along with a call back function. I honestly have no understanding idea what the call back function does, but it works.

Now tell passport to use this strategy:

view plain print about
1passport.use('mySamlStrategy', samlStrategy);

All good. I gave the SamlStrategy 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:

view plain print about
2 (req: Request, res: Response, next: NextFunction) =>
3 passport.authenticate('mySamlStrategy') (req, res, next);
4 }

This is an ExpressJS snippet, since the microservice uses ExpressJS.

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 samle callback:

view plain print about
2 (req: Request, res: Response, next: NextFunction) =>
3 passport.authenticate('mySamlStrategy')(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 }

The callback just uses our passport SAML Strategy to process the results. Then it does something to validate the results, and then 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.