
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
03-28-2022 06:54 AM
Hi,
We have started seeing issues with some users who were unable to login to ServiceNow. We use SSO which authenticates with the users email address.
These users all have 2 records on sys_user (there are reasons - I wont bore you with as to why they have 2 records). One active, one inactive. Up until recently, they could login fine as was authenticating against the active record. It now seems to be ignoring the active record and only authenticating against the inactive record and therefore not allowing the user to login.
Could this be related to indexing? What should I be looking for?
Solved! Go to Solution.

- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
03-29-2022 07:40 PM
Sorry for the delay; This is what I believe you should be able to do.
Be SURE to test this in a lower environment quite extensively - don't try this in PROD directly for the first time!
Create a new script include in the Global scope, accessible from all scopes.
We're going to extend MultiSSOv2_SAML2_internal script include and override the loginUser method (I added the query to this one).
Name: MultiSSOv2_SAML2_internal_V2
Script:
gs.include("PrototypeServer");
gs.include("SAML2_custom");
var MultiSSOv2_SAML2_internal_V2 = Class.create();
MultiSSOv2_SAML2_internal_V2.prototype = Object.extend(new MultiSSOv2_SAML2_internal(), {
initialize: function() {},
//return user_name or 'failed_authentication'
loginUser: function(subjectUserName) {
var eventLogParm1 = "user_name=" + subjectUserName;
var respType = this.SAML2.isIdPInitiated() ? "IdP" : "SP";
var eventLogParm2 = "initiator=" + respType + ",multisso=true,idpsysid=" + this.propGR.getUniqueValue();
var userField = this.propertiesGR.user_field;
if (subjectUserName == null) {
SNC.SecurityEventSender.sendSAMLLoginFailureEventData("", eventLogParm2);
return this.propertiesGR.failed_requirement_redirect;
}
if (!SNC.AuthenticationHelper.isUsernameValid(subjectUserName)) {
SNC.SecurityEventSender.sendSAMLLoginFailureEventData(eventLogParm1, eventLogParm2);
this.SAML2.logError(gs.getMessage("Subject Username validation failed"));
return "failed_authentication";
}
if (!userField || userField == '') {
var errorMessage = gs.getMessage("User Field validation failed");
SNC.SSOUtils.writeLogSummary(false, errorMessage, gs.getMessage("Ensure that the 'User Field' field is not null or blank"));
this.SAML2.logError(errorMessage);
SNC.SecurityEventSender.sendSAMLLoginFailureEventData(eventLogParm1, eventLogParm2);
return "failed_authentication";
} else if (!GlideTableDescriptor.fieldExists('sys_user', userField)) {
var errorMessage = gs.getMessage("Invalid User Field. {0} is not a field on the sys_user table.", userField);
SNC.SSOUtils.writeLogSummary(false, gs.getMessage("User Field validation failed"), errorMessage);
this.SAML2.logError(errorMessage);
SNC.SecurityEventSender.sendSAMLLoginFailureEventData(eventLogParm1, eventLogParm2);
return "failed_authentication";
}
var ugr = new GlideRecord("sys_user");
ugr.addQuery(this.propertiesGR.user_field, subjectUserName);
ugr.addActivQuery();
ugr.query();
var foundUser = ugr.next();
if (foundUser) {
if (!(this.SAML2.isTestSAMLConnection() || this.isAutoRedirectIDP(this.propertiesGR.sys_id))) {
var userSsoSource = ugr.getValue('sso_source');
var companySysId = ugr.getValue('company');
if (!GlideStringUtil.nil(userSsoSource)) {
var userSso = userSsoSource.split(':');
if (!GlideStringUtil.nil(userSso[1]) && userSso[1] != this.propertiesGR.sys_id) {
var errorMessage = gs.getMessage("Ensure that the user you are trying to login is from the correct source, as mentioned in user's sso source field in servicenow instance.");
this.SAML2.logError(errorMessage);
return;
}
} else if (!GlideStringUtil.nil(companySysId)) {
var cgr = new GlideRecord("core_company");
cgr.get(companySysId);
var companySsoSource = cgr.getValue('sso_source');
if (!GlideStringUtil.nil(companySsoSource)) {
var companySso = companySsoSource.split(':');
if (!GlideStringUtil.nil(companySso[1]) && companySso[1] != this.propertiesGR.sys_id) {
var errorMessage = gs.getMessage("Ensure that the user you are trying to login is from the correct source, as mentioned in company's sso source field for user in servicenow instance.");
this.SAML2.logError(errorMessage);
return;
}
}
}
}
this.logDebug("Test connection or auto-redirect IDP, Skipping SSO source field validation.");
this.importOrUpdateSAMLUser(true);
} else {
if (!this.SAML2.isTestSAMLConnection()) {
this.importOrUpdateSAMLUser(false);
ugr.query(); // query again to make sure import is successful
foundUser = ugr.next();
}
if (!foundUser) {
var errorMessage = gs.getMessage("User: {0} not found", subjectUserName);
SNC.SSOUtils.writeMultipleLogSummary(false, errorMessage, gs.getMessage("Ensure that the user you are trying the test connection with is present in the system."), 'userField');
this.SAML2.logError(errorMessage);
SNC.SecurityEventSender.sendSAMLLoginFailureEventData(eventLogParm1, eventLogParm2);
return "failed_authentication";
}
}
var userName = ugr.getValue("user_name");
if (GlideStringUtil.nil(userName)) {
SNC.SecurityEventSender.sendSAMLLoginFailureEventData(eventLogParm1, eventLogParm2);
this.SAML2.logError("user_name value is empty.");
return "failed_authentication";
}
if (this.SAML2.isTestSAMLConnection()) {
var result = SNC.GlideSAML2Tester.validateUserRecord(ugr);
if ("failed_authentication" == result) {
return result;
}
}
if (!this.SAML2.isTestSAMLConnection()) {
this.redirectURL = request.getSession().getAttribute("SAML_RelayState");
if (!this.redirectURL) {
this.logDebug("SAML_RelayState is not available in the session, try the RelayState in the request.");
this.redirectURL = request.getParameter("RelayState");
}
SNC.SecurityEventSender.sendSAMLLoginSuccessEventData(eventLogParm1, eventLogParm2);
request.getSession().setAttribute("SAML_RelayState", null);
// successfully logged in. we need set sso_id cookie
this.SAML2.saveSSOIdInCookie(this.propertiesGR.sys_id);
request.getSession().setAttribute("glide.authenticate.multisso.login.method", "saml");
}
return userName;
},
type: 'MultiSSOv2_SAML2_internal_V2'
});
Now we need to modify the the MultiSSOv2_SAML2_custom to use our script include:
Since this is an out-of-the-box script you will want to export the record to XML in case you need to restore it, then set the record to active = false. Next, open the record again and use insert and stay to make a copy and set your copy to active.
Then adjust as follows:
gs.include("PrototypeServer");
var MultiSSOv2_SAML2_custom = Class.create();
MultiSSOv2_SAML2_custom.prototype = Object.extend(new MultiSSOv2_SAML2_internal_V2(), {
initialize: function() {
MultiSSOv2_SAML2_internal_V2.prototype.initialize.call(this);
},
type: 'MultiSSOv2_SAML2_custom'
});
Again, be sure to test this in DEV or TEST before trying it in PROD!
I hope this helps!
If this was helpful, or correct, please be kind and mark the answer appropriately.
Michael Jones - Proud member of the GlideFast Consulting Team!
Michael D. Jones
Proud member of the GlideFast Consulting Team!

- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
03-28-2022 10:30 AM
The root of the issue is that the overall SSO process does, indeed, expect the value for Name ID to be unique within the user table - so it will use the first record that matches. Interestingly enough the sort order seems to place the most recently updated record at the bottom of the list (not the top) so it might be that your active records for those impacted users were the ones most recently updated, hence they are now "lower" in the list than then inactive records, causing those records to be used.
You could try updating the inactive records for impacted users and see if that resolves the issue, but that is only a temporary fix.
To keep this from occurring you would need to check you IDP record and see which Single Sign On Script you are using. You would then need to make a copy of that script (you likely can't edit the existing one) and in the loginUser: function() you would need to edit the query used to match a record on sys_user. Maybe something like:
var ugr = new GlideRecord("sys_user");
ugr.addQuery(this.userField, nameId);
ugr.addActiveQuery(); //potentially add this line
ugr.query();
var foundUser = ugr.next();
I am assuming you pre-load all users via a data source (like Azure or LDAP) and do not utilize User Provisioning? If you do use this feature, keep in mind that a change like this could result in a new user being created automatically at login if you did happen to disable both user records (no user would match, so a new user would be created). In that case you would need to find a more meaningful way to differentiate between the two records in the query so that you could still set users as inactive to remove access, etc.
I hope this helps!
If this was helpful, or correct, please be kind and mark the answer appropriately.
Michael Jones - Proud member of the GlideFast Consulting Team!
Michael D. Jones
Proud member of the GlideFast Consulting Team!

- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
03-29-2022 01:46 AM
Morning
Thanks so much for this as agree that this looks like the most likely source of the issue. We recently updated our records following a change in our middleware.
I have checked the IDP record that you suggested and can see that we are using a script (This was implemented before I was involved).
This script being:
gs.include("PrototypeServer");
var MultiSSOv2_SAML2_custom = Class.create();
MultiSSOv2_SAML2_custom.prototype = Object.extend(new MultiSSOv2_SAML2_internal(), {
initialize: function() {
MultiSSOv2_SAML2_internal.prototype.initialize.call(this);
},
type: 'MultiSSOv2_SAML2_custom'
});
This is then calling the read only script: MultiSSOv2_SAML2_internal
As the MultiSSOv2_SAML2_custom script is editable, am I able to add the active query to the extention? And if so, how would I achieve this?
Thanks for all your help, its appreciated.

- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
03-29-2022 07:40 PM
Sorry for the delay; This is what I believe you should be able to do.
Be SURE to test this in a lower environment quite extensively - don't try this in PROD directly for the first time!
Create a new script include in the Global scope, accessible from all scopes.
We're going to extend MultiSSOv2_SAML2_internal script include and override the loginUser method (I added the query to this one).
Name: MultiSSOv2_SAML2_internal_V2
Script:
gs.include("PrototypeServer");
gs.include("SAML2_custom");
var MultiSSOv2_SAML2_internal_V2 = Class.create();
MultiSSOv2_SAML2_internal_V2.prototype = Object.extend(new MultiSSOv2_SAML2_internal(), {
initialize: function() {},
//return user_name or 'failed_authentication'
loginUser: function(subjectUserName) {
var eventLogParm1 = "user_name=" + subjectUserName;
var respType = this.SAML2.isIdPInitiated() ? "IdP" : "SP";
var eventLogParm2 = "initiator=" + respType + ",multisso=true,idpsysid=" + this.propGR.getUniqueValue();
var userField = this.propertiesGR.user_field;
if (subjectUserName == null) {
SNC.SecurityEventSender.sendSAMLLoginFailureEventData("", eventLogParm2);
return this.propertiesGR.failed_requirement_redirect;
}
if (!SNC.AuthenticationHelper.isUsernameValid(subjectUserName)) {
SNC.SecurityEventSender.sendSAMLLoginFailureEventData(eventLogParm1, eventLogParm2);
this.SAML2.logError(gs.getMessage("Subject Username validation failed"));
return "failed_authentication";
}
if (!userField || userField == '') {
var errorMessage = gs.getMessage("User Field validation failed");
SNC.SSOUtils.writeLogSummary(false, errorMessage, gs.getMessage("Ensure that the 'User Field' field is not null or blank"));
this.SAML2.logError(errorMessage);
SNC.SecurityEventSender.sendSAMLLoginFailureEventData(eventLogParm1, eventLogParm2);
return "failed_authentication";
} else if (!GlideTableDescriptor.fieldExists('sys_user', userField)) {
var errorMessage = gs.getMessage("Invalid User Field. {0} is not a field on the sys_user table.", userField);
SNC.SSOUtils.writeLogSummary(false, gs.getMessage("User Field validation failed"), errorMessage);
this.SAML2.logError(errorMessage);
SNC.SecurityEventSender.sendSAMLLoginFailureEventData(eventLogParm1, eventLogParm2);
return "failed_authentication";
}
var ugr = new GlideRecord("sys_user");
ugr.addQuery(this.propertiesGR.user_field, subjectUserName);
ugr.addActivQuery();
ugr.query();
var foundUser = ugr.next();
if (foundUser) {
if (!(this.SAML2.isTestSAMLConnection() || this.isAutoRedirectIDP(this.propertiesGR.sys_id))) {
var userSsoSource = ugr.getValue('sso_source');
var companySysId = ugr.getValue('company');
if (!GlideStringUtil.nil(userSsoSource)) {
var userSso = userSsoSource.split(':');
if (!GlideStringUtil.nil(userSso[1]) && userSso[1] != this.propertiesGR.sys_id) {
var errorMessage = gs.getMessage("Ensure that the user you are trying to login is from the correct source, as mentioned in user's sso source field in servicenow instance.");
this.SAML2.logError(errorMessage);
return;
}
} else if (!GlideStringUtil.nil(companySysId)) {
var cgr = new GlideRecord("core_company");
cgr.get(companySysId);
var companySsoSource = cgr.getValue('sso_source');
if (!GlideStringUtil.nil(companySsoSource)) {
var companySso = companySsoSource.split(':');
if (!GlideStringUtil.nil(companySso[1]) && companySso[1] != this.propertiesGR.sys_id) {
var errorMessage = gs.getMessage("Ensure that the user you are trying to login is from the correct source, as mentioned in company's sso source field for user in servicenow instance.");
this.SAML2.logError(errorMessage);
return;
}
}
}
}
this.logDebug("Test connection or auto-redirect IDP, Skipping SSO source field validation.");
this.importOrUpdateSAMLUser(true);
} else {
if (!this.SAML2.isTestSAMLConnection()) {
this.importOrUpdateSAMLUser(false);
ugr.query(); // query again to make sure import is successful
foundUser = ugr.next();
}
if (!foundUser) {
var errorMessage = gs.getMessage("User: {0} not found", subjectUserName);
SNC.SSOUtils.writeMultipleLogSummary(false, errorMessage, gs.getMessage("Ensure that the user you are trying the test connection with is present in the system."), 'userField');
this.SAML2.logError(errorMessage);
SNC.SecurityEventSender.sendSAMLLoginFailureEventData(eventLogParm1, eventLogParm2);
return "failed_authentication";
}
}
var userName = ugr.getValue("user_name");
if (GlideStringUtil.nil(userName)) {
SNC.SecurityEventSender.sendSAMLLoginFailureEventData(eventLogParm1, eventLogParm2);
this.SAML2.logError("user_name value is empty.");
return "failed_authentication";
}
if (this.SAML2.isTestSAMLConnection()) {
var result = SNC.GlideSAML2Tester.validateUserRecord(ugr);
if ("failed_authentication" == result) {
return result;
}
}
if (!this.SAML2.isTestSAMLConnection()) {
this.redirectURL = request.getSession().getAttribute("SAML_RelayState");
if (!this.redirectURL) {
this.logDebug("SAML_RelayState is not available in the session, try the RelayState in the request.");
this.redirectURL = request.getParameter("RelayState");
}
SNC.SecurityEventSender.sendSAMLLoginSuccessEventData(eventLogParm1, eventLogParm2);
request.getSession().setAttribute("SAML_RelayState", null);
// successfully logged in. we need set sso_id cookie
this.SAML2.saveSSOIdInCookie(this.propertiesGR.sys_id);
request.getSession().setAttribute("glide.authenticate.multisso.login.method", "saml");
}
return userName;
},
type: 'MultiSSOv2_SAML2_internal_V2'
});
Now we need to modify the the MultiSSOv2_SAML2_custom to use our script include:
Since this is an out-of-the-box script you will want to export the record to XML in case you need to restore it, then set the record to active = false. Next, open the record again and use insert and stay to make a copy and set your copy to active.
Then adjust as follows:
gs.include("PrototypeServer");
var MultiSSOv2_SAML2_custom = Class.create();
MultiSSOv2_SAML2_custom.prototype = Object.extend(new MultiSSOv2_SAML2_internal_V2(), {
initialize: function() {
MultiSSOv2_SAML2_internal_V2.prototype.initialize.call(this);
},
type: 'MultiSSOv2_SAML2_custom'
});
Again, be sure to test this in DEV or TEST before trying it in PROD!
I hope this helps!
If this was helpful, or correct, please be kind and mark the answer appropriately.
Michael Jones - Proud member of the GlideFast Consulting Team!
Michael D. Jones
Proud member of the GlideFast Consulting Team!

- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
03-31-2022 01:27 AM
Thanks so much
Will make sure I test it first as you say