You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
I did not know where to go next so I'm going to post my issue here, as I've already seen some related issues on this matter. Unfortunately the solutions provided did not work in my case, and I do not know what to try more.
So some background: I have a NodeJS/ExpressJS/passport-saml application that authenticates against an ADFS system. The SSO part of the matter works perfectly, but I can't seem to get the SLO part working.
What happens is that when I initiate either a SP-initiated or IdP-initiated logout it hangs on the first SP. This first SP is being logged out correctly, but it is then redirected to the login page of the first SP and keeps waiting for the credentials to be entered, effectively halting the redirect chain that has to happen.
What I've tried so far is a lot, including using both POST and HTTP-Redirect bindings on my SLO ADFS endpoint/NodeJS server, modifying the routes etc.
Current implementation is as follows:
SLO endpoint configuration (equal for each SP, the blacked out part contains <sp_host_name>):
The passport-saml configuration is as follows on the SP server:
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ IMPORTS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//
// NodeJS native
const path = require('path');
const fs = require('fs');
// NodeJS packages
const SamlStrategy = require('passport-saml').Strategy;
const { Database } = require('../../Database');
// Custom imports
const { ApplicationConfiguration } = require('../../ApplicationConfiguration');
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ CONSTANTS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//
let strategy = {};
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ INIT ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//
/**
* Initialise the passport saml strategy with the necessary configuration parameters.
*/
const initStrategy = () => {
// Get additional required configuration
const config = ApplicationConfiguration.getProperties([
['CGS_HOST'],
['AUTH_PORT'],
['SSO', 'host'],
['SSO', 'identifier'],
['SSO', 'cert'],
['SSO', 'algorithm'],
['HTTPS_CERT_PRIVATE_PATH'],
]);
// Define the SAML strategy based on configuration
strategy = new SamlStrategy(
{
// URL that should be configured inside the AD FS as return URL for authentication requests
callbackUrl: `https://${<sp_host_name>}:${<sp_port_value>}/sso/callback`,
// URL on which the AD FS should be reached
entryPoint: <idp_host_name>,
// Identifier for the CIR-COO application in the AD FS
issuer: <sp_identifier_in_idp>,
identifierFormat: null,
// CIR-COO private certificate
privateCert: fs.readFileSync(<sp_server_private_cert_path>, 'utf8'),
// Identity Provider's public key
cert: fs.readFileSync(<idp_server_public_cert_path>, 'utf8'),
authnContext: ['urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport'],
// AD FS signature hash algorithm with which the response is encrypted
signatureAlgorithm: <idp_signature_algorithm>,
// Single Log Out URL AD FS
logoutUrl: <idp_host_name>,
// Single Log Out callback URL
logoutCallbackUrl: `https://${<sp_host_name>}:${<sp_port_value>}/slo/callback`,
// skew that is acceptable between client and server when checking validity timestamps
acceptedClockSkewMs: -1,
},
async (profile, done) => {
// Map ADFS groups to Group without ADFS\\ characters
const roles = profile.Roles.map(role => role.replace('ADFS\\', ''));
// Get id's from the roles
const queryResult = await Database.executeQuery('auth-groups', 'select_group_ids_by_name', [roles]);
// Map Query result to Array for example: [1,2]
const groupIds = queryResult.map(group => group.id);
done(null,
{
sessionIndex: profile.sessionIndex,
nameID: profile.nameID,
nameIDFormat: profile.nameIDFormat,
id: profile.DistinguishedName,
username: profile.DistinguishedName,
displayName: profile.DisplayName,
groups: profile.Roles,
mail: profile.Emailaddress,
groupIds,
});
},
);
// Return the passport strategy
return strategy;
};
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ PASSPORT CONFIG ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//
/**
* Initialise the passport instance and add the saml passport strategy to it for authentication
* @param {Object} passport - Passport object
*/
const initPassport = (passport) => {
// (De)serialising
passport.serializeUser((user, done) => {
done(null, user);
});
passport.deserializeUser((user, done) => {
done(null, user);
});
// Initialise the strategy
const passportStrategy = initStrategy();
// Addition strategy to passport
passport.use('saml', passportStrategy);
};
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ HELPERS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//
/**
* Get the metadata from the Service Provider (this server).
* @param {String} publicPath - Path to public certificate
* @return {Promise<any>} - Metadata object for this application
*/
const getMetaData = publicPath => new Promise((resolve) => {
const metaData = strategy.generateServiceProviderMetadata({}, fs.readFileSync(path.join(publicPath), 'utf8'));
resolve(metaData);
});
/**
* Construct a Single Logout Request and send it to the IdP.
* @param {Object} req - Default request object
* @param {Object} res - Default response object
*/
const logout = (req, res) => {
// Construct SLO request for IdP
strategy.logout(req, (err, url) => {
req.logOut();
// Redirect to SLO callback URL and send logout request.
return res.redirect(url);
});
};
const getStrategy = () => strategy;
module.exports = {
initPassport,
getStrategy,
getMetaData,
logout,
};
And the relevant routes and functions are as follows:
const logOutLocalSession = sid => new Promise(((resolve, reject) => {
log.info(`Received request to destroy session with sid ${sid}.`);
// Destroy local session
store.destroy(sid, (err) => {
if (err) {
log.error(`Error occurred while logging out local session with SID ${sid}: ${err}`);
reject('Onbekende fout opgetreden bij uitloggen lokaal.');
}
log.info(`Successfully logged out user locally with SID ${sid}.`);
resolve();
});
}));
const logOutAllSessions = async (req, res) => {
// Extract username to get all sessions
const { username } = req.session.passport.user;
log.info(`Received request to log user ${username} out of all sessions.`);
const sessionIdsRes = await Database.executeQuery('sessions', 'select_sids_by_user_id', [username]);
// Loop over all sessions and destroy them
const destroyPromises = [];
sessionIdsRes.forEach((sessionIdRes) => {
destroyPromises.push(logOutLocalSession(sessionIdRes.sid));
});
await Promise.all(destroyPromises);
// Remove local session from request
req.session = null;
log.info(`User ${username} logged out successfully from all known sessions.`);
};
const logOutIdp = (req, res) => {
const { username } = req.session.passport.user;
log.info(`Received request to log out user ${username} on Identity Provider.`);
const strategy = passportImpl.getStrategy();
// Create logout request for IdP
strategy.logout(req, async (err, url) => {
// Destroy local sessions
logOutAllSessions(req, res);
// Redirect to SLO callback URL and send logout request.
return res.redirect(url);
});
};
// SP initiated logout sequence
app.get('/auth/logout', (req, res) => {
const { username } = req.session.passport.user;
// If user not logged in, redirect to login
if (!req.user) {
return res.redirect('/saml/login');
}
if (username === 'Administrator' || username === 'Support user') {
logOutLocalSession(req.session.id);
} else {
logOutIdp(req, res);
}
});
// IdP initiated logout sequence or from other SP
app.post('/slo/callback', logOutAllSessions);
If there is some information missing I will be able to provide. I do hope I can get some leads on what to try next! Thanks in advance !
The text was updated successfully, but these errors were encountered:
What happens is that when I initiate either a SP-initiated or IdP-initiated logout it hangs on the first SP. This first SP is being logged out correctly, but it is then redirected to the login page of the first SP and keeps waiting for the credentials to be entered, effectively halting the redirect chain that has to happen.
I would have expected to see that your /slo/callback implementation would response with LogoutResponse to incoming LogoutRequest as specified in SAML SLO specification.
NOTE: IdP shall report status of SLO with LogoutResponse to that same callback if SP instance was the one which was used to trigger SLO so prepare to handle also that message properly (as of now your implemenation would trigger yet another logoutAllSessions()). See also #221 (comment)
Furthermore if your IdP is at different TLD as your SPs you might have similar problem thatpassport-saml's own SLO handling implementation has ( see #419 )
Side notes:
You have configured acceptedClockSkewMs: -1 which disables NotOnOrAfter validation (as described in passport-saml's README.md (link to version 3.2.1). It is possible to reuse/replay saved SAML login response over and over again without ever needing to visit IdP (i.e. user whose access is terminated at AD could still login to your SP by posting saved SAML login response).
you have not enabled audience verification. SAML login response targeted to SP1 could be replayed to SP2 and it would accept it as valid login.
Hi there,
I did not know where to go next so I'm going to post my issue here, as I've already seen some related issues on this matter. Unfortunately the solutions provided did not work in my case, and I do not know what to try more.
So some background: I have a NodeJS/ExpressJS/passport-saml application that authenticates against an ADFS system. The SSO part of the matter works perfectly, but I can't seem to get the SLO part working.
What happens is that when I initiate either a SP-initiated or IdP-initiated logout it hangs on the first SP. This first SP is being logged out correctly, but it is then redirected to the login page of the first SP and keeps waiting for the credentials to be entered, effectively halting the redirect chain that has to happen.
What I've tried so far is a lot, including using both POST and HTTP-Redirect bindings on my SLO ADFS endpoint/NodeJS server, modifying the routes etc.
Current implementation is as follows:
SLO endpoint configuration (equal for each SP, the blacked out part contains <sp_host_name>):
The passport-saml configuration is as follows on the SP server:
And the relevant routes and functions are as follows:
If there is some information missing I will be able to provide. I do hope I can get some leads on what to try next! Thanks in advance !
The text was updated successfully, but these errors were encountered: