16

I have a question regarding the proper way to logout a user when using passport-saml for authentication.

The example script with passport-saml shows logging out as this:

app.get('/logout', function(req, res){
  req.logout();
  res.redirect('/');
});

From what I can tell, this will end the local passport session, but it doesn't seem to send a logout request to the SAML IdP. When the user does another login, it redirects to the IdP but immediately redirects back with the authenticated user. Is there a way to logout with the IdP so that the user has to enter their password again when signing in to my site? I've seen other sites that use our IdP do this, so I think it's possible.

I did notice in the passport-saml code that there is a logout() method on the passport-saml Strategy object, which doesn't seem to be called by req.logout(). So I tried switching the code to this:

app.get('/logout', function(req, res) {
    //strategy is a ref to passport-saml Strategy instance 
    strategy.logout(req, function(){
        req.logout();
        res.redirect('/');
    });
});

But I got this error deep in XMLNode.js

Error: Could not create any elements with: [object Object]
   at XMLElement.module.exports.XMLNode.element (/.../node_modules/passport-saml/node_modules/xmlbuilder/lib/XMLNode.js:74:15)
   at XMLElement.module.exports.XMLNode.element (/.../node_modules/passport-saml/node_modules/xmlbuilder/lib/XMLNode.js:54:25)
   at XMLElement.module.exports.XMLNode.element (/.../node_modules/passport-saml/node_modules/xmlbuilder/lib/XMLNode.js:54:25)
   at new XMLBuilder (/.../node_modules/passport-saml/node_modules/xmlbuilder/lib/XMLBuilder.js:27:19)
   at Object.module.exports.create (/.../node_modules/passport-saml/node_modules/xmlbuilder/lib/index.js:11:12)
   at SAML.generateLogoutRequest (/.../node_modules/passport-saml/lib/passport-saml/saml.js:169:21)

Am I not calling this method correctly? Or should I not be calling this method directly and calling some other method instead?

I see that in generateLogoutRequest() it is referring to two properties on the req.user that I'm not sure are there:

  'saml:NameID' : {
    '@Format': req.user.nameIDFormat,
    '#text': req.user.nameID
  }

If these properties are not there, will that cause this error? If so, I assume that maybe I need to ensure that these properties are added to the user object that is returned from the verify callback function?

Thanks for any help anyone might be able to provide on this.

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
Dave Stearns
  • 585
  • 3
  • 6
  • 14

2 Answers2

14

Yes adding the nameIDFormat and nameID to the user will solve the issue.

  1. To enable the logout you should configure the logoutURL option in your strategy

logoutUrl: 'http://example.org/simplesaml/saml2/idp/SingleLogoutService.php',

The logout method in the strategy does not actually send any request. the callback function is called with the request as parameter.

To launch the logout process :

passport.logoutSaml = function(req, res) {
    //Here add the nameID and nameIDFormat to the user if you stored it someplace.
    req.user.nameID = req.user.saml.nameID;
    req.user.nameIDFormat = req.user.saml.nameIDFormat;


    samlStrategy.logout(req, function(err, request){
        if(!err){
            //redirect to the IdP Logout URL
            res.redirect(request);
        }
    });
};

edit: the nameId and nameIdFormat has to be saved somewhere on successful login

var samlStrategy = new SamlStrategy(
  {
    callbackUrl: 'https://mydomain/auth/saml/callback',
    entryPoint: 'https://authprovider/endpoint',
    logoutUrl: 'https://authprovider/logoutEndPoint',
    issuer: 'passport-saml'
  },
  function(profile, done) {

      //Here save the nameId and nameIDFormat somewhere
      user.saml = {};
      user.saml.nameID = profile.nameID;
      user.saml.nameIDFormat = profile.nameIDFormat;

      //Do save

      });
  });
  1. You will also have to create an end point for the logout callback :

This URL should be configured in your SP metadata in your IdP configuration. The IdP will redirect to that URL once the logout is done.

in your routes :

app.post('/auth/saml/logout/callback', passport.logoutSamlCallback);

In your passport configuration :

passport.logoutSamlCallback = function(req, res){
    req.logout();
    res.redirect('/');
}
bpavot
  • 179
  • 6
  • I don't have them in Login PostBack! Where can I find nameID and nameIDFormat?! – DaNeSh May 17 '16 at 16:08
  • 2
    @DaNeSh you have it in the profile parameter in the stratedy callback – bpavot May 19 '16 at 07:09
  • for me it worked with any nameIDFormat, it just shouldn't be empty! – DaNeSh May 19 '16 at 16:04
  • 1
    This works for me, but could someone please elaborate on what the `logout` function of `Strategy` actually does? I can't find documentation anywhere. I can only find info on `req.logout()`. – Raphael Dec 10 '20 at 01:33
  • Hi @bpavot can you please provide some link to follow, I am unable to get it working? – Kashif Ullah Oct 14 '21 at 13:49
  • The answer says "The logout method in the strategy does not actually send any request. ". When exactly is SAML SLO request is sent? – sql_dummy Aug 07 '23 at 05:52
0

For me @bpavot answer's worked after some tweaking. Below are the changes that worked for me. Hope this might help somebody else struggling with this.

  var passport = require('passport');

  // .... add other passport config, ie: serializeUser, deserializeUser

  var samlStrategy = new SamlStrategy({
    callbackUrl: config.callbackUrl,
    entryPoint: config.entryPoint,
    logoutUrl: config.logoutUrl,

    // ..... add other properties as needed

  }, function (profile, done) {
      return done(null, 
        {
          displayName: profile.displayname,
          email: profile.nameID, 

          // ...... add other props as needed

          // these 2 props below will be used later when logging out
          nameID: profile.nameID,
          nameIDFormat: profile.nameIDFormat,
        });
  });

  passport.use(samlStrategy);

  // ..... other app logic/routing here

  app.get('/logout', function (req, res) {
      // destroy the passport session
      req.session.destroy(function (err) {
        req.logout();
        res.clearCookie('connect.sid');
        // destroy the IdP session
        samlStrategy.logout(req, function (err, request) {
        if (!err) {
            // redirect to the IdP Logout URL (ie: config.logoutUrl)
            // the IdP will later redirect to the intended app logout url 
            // which should be configured in IdP
            res.redirect(request);
        } else {
            res.redirect('/logout-error');
        }
      });
    });
  });
artemisian
  • 2,976
  • 1
  • 22
  • 23
  • Are you sure you logged out of IDP? I do not see you have thrid argument for `new SamlStrategy` , which is for logout ( https://github.com/node-saml/passport-saml/blob/0e34bc865b83042533c72a84e34944ef7a383a2a/README.md?plain=1#L95) – sql_dummy Aug 07 '23 at 05:43