跳转到主要内容

Single sign-on (SSO) is a property of identity and access management (IAM) that enables users to securely authenticate with multiple applications and websites by logging in only once with just one set of credentials (username and password).

Here in this article, we will Implement Single Sign-On in a Node.js App with SAML authentication strategy using Passport.js middleware if you want to read more on Single Sign-on and SAML you can read this article below.

Single Sign-On(SSO) — SAML Authentication Explained

What is Single sign-on?

medium.com

First, we need an Identity Provider which can be an organization’s active directory or any free identity provider like OneLogin. For testing purposes I used OneLogin identity provider it allows us to create a developer account and we can play around it https://developers.onelogin.com/

We can configure our App please follow documentation for more information.

We will configure this information from the SAML-Test connector into our Passport.js middleware.

This includes certificate, endpoint ie. entry point will be the entry point for our app this will redirect our app on the authentication page.

We need to configure our app information into the identity provider to establish trust.

Application details are all Service Provider’s configuration like issuer and consumer callback URL

Consumer URL we will configure Service Provider’s Callback Url which will be called on authentication success.

Above information provided by the identity provider which will set up trust between an identity provider and service provider. Here service provider will be a backend app that will make trust between multiple identity providers like Azure IDP, OneLogin, Google OAuth, and so on.

{
    entryPoint: 'https://ad.example.net/adfs/ls/',
    issuer: 'DEV-BrightLab',
    callbackUrl: 'https://your-app.example.net/login/callback',
    cert: 'MIICizCCAfQCCQCY8tKaMc0BMjANBgkqh ... W=='
 }

Here we have details which we will set up in our service provider to establish a trust between the service provider and identity provider.

entryPoint: is the URL provided by Identity Provider which will be used to redirect users on the login page if not authenticated.

issuer: is a string provided to the Identity Provider to uniquely identify Service Provider.

callbackUrl: This will be the URL of the Service Provider which will consume the SAML response once authentication is done on Identity Provider. Identity Provider will call this URL.

cert: This is the certificate provided by Identity Provider. This will be used to establish the trust between Identity Provider and Service Provider.

Now we are ready to implement our service provider using Passport.js

Passport is authentication middleware for Node.js. extremely flexible and modular, Passport can be unobtrusively dropped into any Express-based web application. A comprehensive set of strategies support authentication using a username and password, Facebook, Twitter, and more.

npm install passport

Install passport-saml, it is a SAML 2.0 authentication provider for Passport, the Node.js authentication library. The code was originally based on Michael Bosworth’s express-saml library.

npm install passport-saml

First We will Setup our passport handler middleware with our identity provider configuration. This is for a single identity provider strategy.

const passport = require('passport');
const passportSaml = require('passport-saml');

passport.serializeUser((user, done) => {
  done(null, user);
});

passport.deserializeUser((user, done) => {
  done(null, user);
});

// SAML strategy for passport -- Single IPD
const strategy = new passportSaml.Strategy(
  {
    entryPoint: process.env.SSO_ENTRYPOINT,
    issuer: process.env.SSO_ISSUER,
    callbackUrl: process.env.SSO_CALLBACK_URL,
    cert: process.env.SSO_CERT,
  },
  (profile, done) => done(null, profile),
);

passport.use(strategy);

module.exports = passport;

Now we can configure this Passport middleware into our app and initialize it.

const express = require('express');
const helmet = require('helmet');
const {
  passport,
} = require('./config');

const {
  userRouter: userV1Router,
} = require('./server/v1/routes');

const { errorHandler } = require('./server/v1/middlewares');

const app = express();

app.use(express.urlencoded({
  extended: true,
}));

app.use(express.json({ limit: '15mb' }));
app.use(helmet());

app.use(passport.initialize());
app.use(passport.session());

//= ==========Registering Router==========
app.use('/user/v1/', userV1Router);

//= ======ERROR Handler
app.use(errorHandler);

module.exports = app;
  • We will set up two routes one is the GET route which will be the entry point of SSO into the application. when it will be called passport will authenticate and verify the trust in between service provider and the identity provider.
  • Another will be the consumer which will be called by the identity provider once it validated the credentials. Here Request body will be a SAML which is a base64 encoded body here I used the Saml2js module to parse the assertion in the SAML body which will provide the details of user who logged in.

Here is the router code for the Router

const express = require('express');
const useragent = require('useragent');
const Saml2js = require('saml2js');

const { passport } = require('../../../../config');

const {
  handler,
} = require('../../middlewares');

const {
  userLogin,
} = require('./../../controller');

const userAgentHandler = (req, res, next) => {
  const agent = useragent.parse(req.headers['user-agent']);
  const deviceInfo = Object.assign({}, {
    device: agent.device,
    os: agent.os,
  });
  req.device = deviceInfo;
  next();
};

const router = express.Router();

/**
 * This Route Authenticates req with IDP
 * If Session is active it returns saml response
 * If Session is not active it redirects to IDP's login form
 */
router.get('/login/sso',
  passport.authenticate('saml', {
    successRedirect: '/',
    failureRedirect: '/login',
  }));

/**
 * This is the callback URL
 * Once Identity Provider validated the Credentials it will be called with base64 SAML req body
 * Here we used Saml2js to extract user Information from SAML assertion attributes
 * If every thing validated we validates if user email present into user DB.
 * Then creates a session for the user set in cookies and do a redirect to Application
 */
router.post('/login/sso/callback',
  userAgentHandler,
  passport
    .authenticate('saml', { failureRedirect: '/', failureFlash: true }), (req, res, next) => {
    const xmlResponse = req.body.SAMLResponse;
    const parser = new Saml2js(xmlResponse);
    req.samlUserObject = parser.toObject();
    next();
  },
  (req, res) => userLogin.createUserSession(res, req));

module.exports = router;

This is a user login controller which creates the session set in response cookies and redirect response to a UI Application page.

Let's put this all together and see how all this works…..

First, we call GET API localhost:9000/user/v1/users/login/sso

  • This GET API call checks if the active session is there or not, In my case, there was no active session it redirects me on an identity provider login page.
  • Once I entered my username and then password it authenticate request on identity provider and then it called the callback URL of service provider which we configured in the identity provider.
  • This POST callback URL localhost:9000/user/v1/users/login/sso/callback created a session for the application by validating request and redirects to the application dashboard page.

Well, We have successfully configured SAML based Single Sign-On

What if we have a SAAS based multitenant application which has different organizations and we need to configure Single Sign-On with these organization's Active Directory or their identity provider, In this case, we can use Passport’s MultiSAML strategy where we can configure SAML configuration on demand, we can identify organizations by subdomain or we can pass path params to these above URL. Let's configure MultiSamlStrategy

const passport = require('passport');
const MultiSamlStrategy = require('passport-saml/multiSamlStrategy');
const { cassandraClient } = require('../../config');

passport.serializeUser((user, done) => {
  done(null, user);
});

passport.deserializeUser((user, done) => {
  done(null, user);
});

const fetchSamlConfig = (request, done) => {
  const orgId = request.params.id;
  cassandraClient.instance.TenantSSOConfig.findOne({ orgId }, (err, org) => {
    if (err) {
      return done(err);
    }
    return done(null, JSON.parse(org.config));
  });
};

// saml strategy for passport
const strategy = new MultiSamlStrategy(
  {
    passReqToCallback: true, // makes req available in callback
    getSamlOptions(request, done) {
      fetchSamlConfig(request, done);
    },
  },
  (req, profile, done) => {
    done(null, profile);
  },
);
passport.use(strategy);

module.exports = passport;

Here getSamlOptions function will be invoked on passport.authenticate() middleware called here we are fetching SAML configuration from DB on the basis of request orgId and setting this config for this middleware.

Here we configure Routes with path params and also configured their value into identity provider.

He is how I configured my BrightLab application with my Organization’s Active Directory ie. Azure.

we provided callback URL as /login/sso/callback/orgNamehere orgName becomes the value for the path parm id and we fetch SAML configuration from DB by {orgId: orgName} and on calling login API it will be redirected to the meck azure login page.

On submitting id and password identity provider authenticates the request and calls the callback URL and Land on the dashboard Page. similarly, we can pass different org if we have configured it and stored it into our database and we can determine this on the basis on the subdomain as well.

If you want to implement multitenancy into the Node.js application you can read this article below.

Multitenant Node.js Application with mongoose (MongoDB)

Multitenancy is a reference to the mode of operation of software where multiple independent instances of one or…

medium.com

References :

Documentation

Passport is authentication middleware for Node. It is designed to serve a singular purpose: authenticate requests. When…

www.passportjs.org