ExpressJS/Passport-SAML Single Log Out re-logs in directly

2.6k Views Asked by At

Currently I am working on a passport-saml implementation in our NodeJS application. The reason to do so is to give our customers the possibility to connect to their AD FS systems and take advantage of SingleSignOn(SSO).

As we also want to give logout functionality I was working on that logic. However, I can't seem to get this simple piece of functionality working. I have already googled a lot, tried a lot of variations and configurations but unfortunately, it does not work.

I would like to give our customers the possibility to SingleLogOut (SLO) that is both SP and IdP driven. This was my starting point. During the course of debugging and development I already took a step back and tried to kill the local session but even that is not possible it seems.

This is the relevant code from the routes I configured for SAML:

const isAuthenticated = (req, res, next) => {
    if (req.isAuthenticated()) {
      // User logged in, pass on to next middleware
      console.info('User authenticated');
      return next();
    }
    // User not logged in, redirect to login page
    console.info('User not authenticated');
    return res.redirect('/login');
  };

// GET-routes
  app.get('/',
    isAuthenticated,
    (req, res) => {
      res.send('Authenticated');
    });

  app.get('/login',
    passport.authenticate('saml', {
      successRedirect: '/',
      failureRedirect: '/login/fail',
    }));

  app.get('/logout',
    (req, res) => {
      passport._strategy('saml').logout(req, (err, url) => {
        return res.redirect(url);
      });
    });

// POST-routes
  app.post('/adfs/callback',
    (req, res, next) => {
      passport.authenticate('saml', (err, user) => {
        // If error occurred redirect to failure URL
        if (err) return res.redirect('/login/fail');
        // If no user could be found, redirect to failure URL
        if (!user) return res.redirect('/login/fail');
        // User found, handle registration of user on request
        req.logIn(user, (loginErr) => {
          if (loginErr) return res.status(400).send(err);
          // Request session set, put in store
          store.set(req.sessionID, req.session, (storeErr) => {
            if (storeErr) return res.status(400).send(storeErr);
            return res.redirect('/');
          });
        });
      })(req, res, next);
    });

  app.post('/logout/callback', (req, res) => {
    // Destroy session and cookie
    store.destroy(req.sessionID, async (err) => {
      req.logout();
      return res.redirect('/');
    });
  });

As can be seen I took control of the session store handling (setting and destroying sessions, but if this is unwise please advise). The session store implemented is the MemoryStore (https://www.npmjs.com/package/memorystore).

What happens is that, when a user is logged in everything works fine. Then a request is sent to route /logout, and some stuff happend and I can see the session changing, the session ID gets changed as well as relevant parameters for passport-saml (nameID, sessionIndex) and the user is then rerouted to '/'.

However then the user is seen as not authenticated and rerouted to '/login'. One would argue that it stops here, as the credentials have to be re-entered. This is not the case, as the user is directly logged in again, without re-entering credentials and I do not know how to prevent this.

I do hope anybody knows what's going on :) If there is need for additional information I would like to hear gladly.

2

There are 2 best solutions below

0
On

Using authnContext: ["urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport"] in the session setup will show the username and password page again after logging out.

Logout achieved https://adfs-url/adfs/ls/?wa=wsignout1.0.

A new session-id is created after signing in again.

2
On

So after much research and investigation I did found the solution for this problem. The trick was in the definition of the passport-saml package, in particular the authncontext parameter.

So previously I had the SamlStrategy options defined as:

{
  // URL that should be configured inside the AD FS as return URL for authentication requests
  callbackUrl: `<URL>`,
  // URL on which the AD FS should be reached
  entryPoint: <URL>,
  // Identifier for the CIR-COO application in the AD FS
  issuer: <identifier>,
  identifierFormat: null,
  // CIR-COO private certificate
  privateCert: <private_cert_path>,
  // Identity Provider's public key
  cert: <cert_path>,
  authnContext: ["urn:federation:authentication:windows"],
  // AD FS signature hash algorithm with which the response is encrypted
  signatureAlgorithm: <algorithm>,
  // Single Log Out URL AD FS
  logoutUrl: <URL>,
  // Single Log Out callback URL
  logoutCallbackUrl: `<URL>`,
}

But after much research I realised this authentication:windows option was the culprit, so I changed it to:

{
  // URL that should be configured inside the AD FS as return URL for authentication requests
  callbackUrl: `<URL>`,
  // URL on which the AD FS should be reached
  entryPoint: <URL>,
  // Identifier for the CIR-COO application in the AD FS
  issuer: <identifier>,
  identifierFormat: null,
  // CIR-COO private certificate
  privateCert: <private_cert_path>,
  // Identity Provider's public key
  cert: <cert_path>,
  authnContext: ["urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport",
    "urn:federation:authentication:windows"],
  // AD FS signature hash algorithm with which the response is encrypted
  signatureAlgorithm: <algorithm>,
  // Single Log Out URL AD FS
  logoutUrl: <URL>,
  // Single Log Out callback URL
  logoutCallbackUrl: `<URL>`,
},

Which basically means it won't retrieve the Windows credentials of the user that is logged onto the system by default, thus redirecting to the login screen of the ADFS server.