Getting started with SAML, Azure and Nodejs

Step to step tutorial for 'SAML Integration in frontend?'

September 6, 2019 - 4 minute read -
code saml tutorial azure-active-directory

Introduction

Security Assertion Markup Language (SAML, pronounced SAM-el) is an open standard for exchanging authentication and authorization data between parties, in particular, between an identity provider and a service provider.

It’s used for logging users into applications based on their sessions in another context. This single sign-on (SSO) login standard has significant advantages over logging in using a username/password:

  • No need for credentials
  • No need to remember and change passwords
  • No weak passwords

Most organizations already know the identity of users because they are logged in to their Active Directory domain or intranet. It makes sense to use this information to log users into other applications, such as web-based applications, and one of the more elegant ways of doing this is by using SAML.

SAML is very powerful and flexible, but the specification can be quite a handful.

How SAML works?

SAML SSO works by validating user identity from an identity provider, for example from Azure active directory by validating the user’s identity from azure active directory (the identity provider) to web application (the service provider). This is done through an exchange of digitally signed XML documents.

Steps:

  • A user is accessing a web application (the service provider).

  • User enters an email address, the application identifies the user’s origin by email domain, based on domain web server (nodejs) create SAML Request and send to the identity provider (Azrue active directoiry), asking for authentication.

  • The user either has an existing active browser session with the identity provider or establishes one by logging into the identity provider.

  • The identity provider builds the authentication response in the form of an XML-document containing the user’s email address, signs it using an X.509 certificate, and posts this information to the service provider.

  • The service provider, which already knows the identity provider and has a certificate fingerprint, retrieves the authentication response and validates it using the certificate fingerprint (using passport library available for nodejs).

  • After identity established, service provider callback the return URL extracting user’s email address and other details.

  • The application validates user identity and the user is provided with application access.

Flow Diagram


SAML request

Implementation

1. User access application

<!--  entryPoint=https://login.microsoftonline.com/123456-xxxx-xxxx-xxxx-123456/saml2 -->
<button class="btn-submit">
  <a :href="config.entryPoint">SSO</a>
</button>

2. Create SAML Request

const moment = require('moment')
// for deflating SAML request
const pako = require('pako')
const uuid = require('uuid')
.....

const config = {
   // domain of email address for identifying on client side if SAML is enable for same or not
  "domain": "outlook.com",
   // unique entityId while setting up SAML server
  "entityURI": "https://example.com",
  // SAML server login url, can be found in SAML XML file
  "entryPoint": "https://login.microsoftonline.com/123456-xxxx-xxxx-xxxx-123456/saml2",
  // X 509 signing certificate, can be found in SAML XML file
  "certificate": "MIIC8DCCAdigAwIBAgIQaKWqA3vVf7ZPHf5h52SkkjANBgkqhkiG9w0BAQsFADA0MTIwMAYDVQQDEylNaWNyb3NvZnQgQXMIIC8DCCAdigAwIBAgIQaKWqA3vVf7ZPHf5h52SkkjANBgkqhkiG9w0BAQsFADA0MTIwMAYDVQQDEylNaWNyb3NvZnQgQX8MIIC8DCCAdigAwIBAgIQaKWqA3vVf7ZPHf5h52SkkjANBgkqhkiG9w0BAQsFADA0MTIwMAYDVQQDEylNaWNyb3NvZnQgQX",
  // callback url after successfully authentication
  "entityReplyUrl": "https://example.com/api/auth/saml"
}

.....

generateSAMLRequest (config) {
  const SAMLReq = `<samlp:AuthnRequest
    xmlns="urn:oasis:names:tc:SAML:2.0:metadata"
    ID="${config.domain}${uuid.v4()}"
    Version="2.0"
    IssueInstant="${moment().utc().format()}"
    IsPassive="false"
    AssertionConsumerServiceURL="${config.entityReplyUrl}" xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
    ForceAuthn="false">
      <Issuer xmlns="urn:oasis:names:tc:SAML:2.0:assertion">
        ${config.entityURI}
      </Issuer>
      <samlp:NameIDPolicy Format="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified">
      </samlp:NameIDPolicy>
    </samlp:AuthnRequest>`

  const deflatedSAMLReq = pako.deflateRaw(SAMLReq)
  const deflatedBase64SAMLReq = Buffer(deflatedSAMLReq).toString('base64')
  const encodedDeflatedBase64SAMLReq = encodeURIComponent(deflatedBase64SAMLReq)

  return ~config.entryPoint.indexOf('?')
    ? `${config.entryPoint}&SAMLRequest=${encodedDeflatedBase64SAMLReq}`
    : `${config.entryPoint}?SAMLRequest=${encodedDeflatedBase64SAMLReq}`
}

3. Identity Established & SAML Response returned

4. App Verifies SAML Response & Log User in

Configure strategy for multiple providers You can pass a getSamlOptions parameter to MultiSamlStrategy which will be called before the SAML flows. Passport-SAML will pass in the request object so you can decide which configuation is appropriate.

var MultiSamlStrategy = require('passport-saml/multiSamlStrategy');

.....

passport.use(new MultiSamlStrategy(
  {
    getSamlOptions: function(request, done) {
      findProvider(request, function(err, provider) {
        if (err) {
          return done(err);
        }
        return done(null, provider.configuration);
      });
    }
  },
  function(profile, done) {
    findByEmail(profile.email, function(err, user) {
      if (err) {
        return done(err);
      }
      return done(null, user);
    });
  })
);