Skip to main content

Implement Single Sign-On SAML strategy with Node.js & Passport.js

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 provide

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.

















Comments

Popular posts from this blog

4 Ways to Communicate Across Browser Tabs in Realtime

1. Local Storage Events You might have already used LocalStorage, which is accessible across Tabs within the same application origin. But do you know that it also supports events? You can use this feature to communicate across Browser Tabs, where other Tabs will receive the event once the storage is updated. For example, let’s say in one Tab, we execute the following JavaScript code. window.localStorage.setItem("loggedIn", "true"); The other Tabs which listen to the event will receive it, as shown below. window.addEventListener('storage', (event) => { if (event.storageArea != localStorage) return; if (event.key === 'loggedIn') { // Do something with event.newValue } }); 2. Broadcast Channel API The Broadcast Channel API allows communication between Tabs, Windows, Frames, Iframes, and  Web Workers . One Tab can create and post to a channel as follows. const channel = new BroadcastChannel('app-data'); channel.postMessage(data); And oth...

Working with Node.js streams

  Introduction Streams are one of the major features that most Node.js applications rely on, especially when handling HTTP requests, reading/writing files, and making socket communications. Streams are very predictable since we can always expect data, error, and end events when using streams. This article will teach Node developers how to use streams to efficiently handle large amounts of data. This is a typical real-world challenge faced by Node developers when they have to deal with a large data source, and it may not be feasible to process this data all at once. This article will cover the following topics: Types of streams When to adopt Node.js streams Batching Composing streams in Node.js Transforming data with transform streams Piping streams Error handling Node.js streams Types of streams The following are four main types of streams in Node.js: Readable streams: The readable stream is responsible for reading data from a source file Writable streams: The writable stream is re...

Certbot SSL configuration in ubuntu

  Introduction Let’s Encrypt is a Certificate Authority (CA) that provides an easy way to obtain and install free  TLS/SSL certificates , thereby enabling encrypted HTTPS on web servers. It simplifies the process by providing a software client, Certbot, that attempts to automate most (if not all) of the required steps. Currently, the entire process of obtaining and installing a certificate is fully automated on both Apache and Nginx. In this tutorial, you will use Certbot to obtain a free SSL certificate for Apache on Ubuntu 18.04 and set up your certificate to renew automatically. This tutorial will use a separate Apache virtual host file instead of the default configuration file.  We recommend  creating new Apache virtual host files for each domain because it helps to avoid common mistakes and maintains the default files as a fallback configuration. Prerequisites To follow this tutorial, you will need: One Ubuntu 18.04 server set up by following this  initial ...