passport.jssamlpassport-saml

passport-saml - express - redirected url not submitting form gives SAML assertion not yet valid


Below is the error that I am getting on my console today as opposed to yesterday when the same code was working fine.

Error: SAML assertion not yet valid
        at SAML.checkTimestampsValidityError

I have verified that I receive a success from the IDP and hence the application gets redirected to the '/home' endpoint in the URL which has been mentioned in the config file.

Additionally, when I submit the form, after an auto redirection [which shows me Internal Server Error]

enter image description here

I press refresh button of the browser and a form submission happens and the expected result is achieved.

enter image description here

My problem is, why doesn't this happens automatically or how and where can I do this submission programatically.

passport.js

const SamlStrategy = require('passport-saml').Strategy;

module.exports = function (passport, config) {

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

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

  passport.use(new SamlStrategy(
    {
      entryPoint: config.passport.saml.entryPoint,
      issuer: config.passport.saml.issuer,
      cert: config.passport.saml.cert,
      path: config.passport.saml.path,
      identifierFormat: config.passport.saml.identifierFormat
    },
    function (profile, done) {

      debugger;
      return done(null,
        {
          sessionIndex: profile.sessionIndex,
          nameID: profile.nameID,
          lastName: profile.lastName,
          firstName: profile.firstName,
          gid: profile.gid,
          county: profile.county,
          mail: profile.mail,
          companyUnit: profile.companyUnit,
          preferredLanguage: profile.preferredLanguage,
          orgCode: profile.orgCode,
          email: profile.email
        });
    })
  );

};

config.js

module.exports = {
      passport: {
        strategy: 'saml',
        saml: {
          callbackUrl: '/home',
          path: '/home',
          entryPoint: 'https://.../GetAccess/Saml/IDP/SSO/Unsolicited?GA_SAML_SP=APP',
          issuer: '...',
          cert: '...',
          identifierFormat: null
        }
      }
  };

app.js

import express from 'express';
import helmet from 'helmet';
import cookieParser from 'cookie-parser';
import bodyparser from 'body-parser';
import path from 'path';
import logger from 'morgan';
import cors from 'cors';
import passport from 'passport';
import session from 'cookie-session';

const config = require('./config.js');
require('./passport')(passport, config);
var app = express();
app.use(logger('dev'));
app.use(cookieParser());
app.use(bodyparser.json());
app.use(bodyparser.urlencoded({ extended: false }));
app.use('/public', express.static(path.join(__dirname, '../public')));
app.use('/data', express.static(path.join(__dirname, '../uploads/')));
app.use(session(
  {
    resave: true,
    saveUninitialized: true,
    secret: 'secret value'
  }));
app.use(passport.initialize());
app.use(passport.session());
app.use(helmet());
app.use(cors());

require('../router/routeConfig')(app, config, passport);

module.exports = app;

routeConfig.js

module.exports = function (app, config, passport) {


  app.get('/', passport.authenticate(config.passport.strategy, {
    successRedirect: '/home',
    failureRedirect: 'https://.../GetAccess/Saml/IDP/SSO/Unsolicited?GA_SAML_SP=APP'
  }));

  app.get('/app', passport.authenticate(config.passport.strategy, {
    successRedirect: '/home',
    failureRedirect: 'https://.../GetAccess/Saml/IDP/SSO/Unsolicited?GA_SAML_SP=APP'
  }));

  app.post(config.passport.saml.path,
    passport.authenticate(config.passport.strategy,
      {
        failureRedirect: 'https://.../GetAccess/Saml/IDP/SSO/Unsolicited?GA_SAML_SP=APP',
        failureFlash: true
      }),
    function (req, res) {
      debugger;
      res.sendFile(path.join(__dirname, "../public/index.html"));
    });

};

Solution

  • Finally I have figured this out after some research,

    As we understand that there are two parties involved in the SAML Authentication Process i.e. IDP and SP, therefore there are certain conditions between these which are supposed to be met, as part of the contract. One such condition is the TIME.

    <saml:Conditions NotBefore="2019-08-01T11:02:49Z" NotOnOrAfter="2019-08-01T11:03:59Z">
    

    This is a tag that I have clipped from the saml response received from the IDP, here the time of my(SP's) server should be between NotBefore and NotOnOrAfter during the process of authentication.

    Therefore, I need to calibrate the clock of my server by a few seconds so that I fit in the time-slice of NotBefore and NotOnOrAfter of the server.

    Of course that is not the way this should be done, but some +n or -n minutes should be allowed from the IDP side (importantly both SP and IDP to follow UTC times).

    More on this topic can be found here,

    SAML Assertion NotBefore,NotOnOrAfter problem due to unsynced clocks : Explained

    ADFS Not Before Time Skew

    Bonus

    Base 64 to XML Decoder

    XML Prettifier

    Edit :

    As mentioned in the comment below, The skew can be configured on either side (IdP > or SP) or both sides. From passport-saml docs: acceptedClockSkewMs: Time in milliseconds of skew that is acceptable between client and server when checking OnBefore and NotOnOrAfter assertion condition validity timestamps. Setting to -1 will disable checking these conditions entirely. Default is 0.

    Mentioned Here