I recently had a customer ask me a question dealing with validating a username entered at login in ServiceNow. This question prompted me to write this post. The ServiceNow login mechanism works exactly how you would expect it to. If a valid username and password are provided, the user is allowed into the system. If you want to perform additional validation on a username or check for a role, then you’ve got to add that logic somehow. This post explains how you can override the out-of-box login routine to perform some custom validation before allowing login to your ServiceNow instance. I’ll give you a couple of common installation exits that I’ve created and used before.
The default login behavior in ServiceNow is handled by the ‘Login’ installation exit. Installation exits are found by navigating in the left nav to ‘System Definition -> Installation Exits’. The examples given here are designed to be used as overrides to the ‘Login’ installation exit.
Restrict Login by Role in Non-Production Instances–
One request I see commonly is for users to be able to log on to a production environment, but only certain users should be able to log on to the other development and test environments. Activating the installation exit below (which overrides the ‘Login’ installation exit) allows you to do this. It checks any incoming login attempt and sees if the user provided has the ‘admin’ role. If they don’t, they are alerted accordingly. If you decide to let all users into the system, you can simply de-activate the ‘AdminOnlyLogin’ installation exit record included below and everyone will be able to log in again.
Name: AdminOnlyLogin (Note: It is critical that this name matches the class name in the script below)
Overrides: Login (Note: The value here must match the name of the ‘Login’ Installation Exit exactly)
Active: True
Script:
var AdminOnlyLogin = Class.create();
AdminOnlyLogin.prototype = {
initialize : function() {
},
process : function() {
// the request is passed in as a global
var userName = request.getParameter("user_name");
var userPassword = request.getParameter("user_password");
if (typeof GlideUser != 'undefined')
var user = GlideUser;
else
var user = Packages.com.glide.sys.User;
//Query to see if the user has the 'admin' role
var isAdmin = false;
var rec = new GlideRecord('sys_user');
rec.addQuery('user_name', userName);
rec.query();
if(rec.next()){
//Query the roles table for this user
var rec1 = new GlideRecord('sys_user_has_role');
rec1.addQuery('user', rec.sys_id);
rec1.query();
while(rec1.next()){
if(rec1.role.getDisplayValue() == 'admin'){
isAdmin = true;
break;
}
}
}
var authed = user.authenticate(userName, userPassword);
//Allow access if the user is an admin
if((authed && isAdmin) || (authed && userName.indexOf('@snc') > -1)){
return user.getUser(userName);
}
this.loginFailed();
//Alert if the user is not an admin
if(!isAdmin){
gs.addErrorMessage('You must be a ServiceNow admin to access this system.');
}
return "login.failed";
},
loginFailed : function() {
if (typeof GlideSysMessage != 'undefined')
var sysMessage = GlideSysMessage;
else
var sysMessage = Packages.com.glide.ui.SysMessage;
var message = sysMessage.format("login_invalid");
if (typeof GlideSession != 'undefined')
var session = GlideSession.get();
else
var session = Packages.com.glide.sys.GlideSession.get();
session.addErrorMessage(message);
var userName = request.getParameter("user_name");
if (typeof GlideEventManager != 'undefined')
var EventManager = GlideEventManager;
else
var EventManager = Packages.com.glide.policy.EventManager;
if (typeof GlideTransaction != 'undefined')
var t = GlideTransaction.get();
else
var t = Packages.com.glide.sys.Transaction.get();
EventManager.queue("login.failed", "", userName, "");
}
}
Alert User on UNC (domain\username) Login Attempt–
Another situation I’ve come across before deals with the way that users should log into a ServiceNow instance. If you’ve integrated your instance with LDAP then you’ll have users using their domain credentials to authenticate. Some users get in the habit of specifying the domain along with their username in a Universal Naming Convention (UNC) format (Domain\User). ServiceNow just needs a username and password so authentication will fail if UNC format is used to authenticate.
You can use an installation exit to help manage this scenario…either to parse out the domain portion of the username if it is found, or to alert the user if a backslash is included in their username as shown in the installation exit below.
Name: DomainAlertLogin (Note: It is critical that this name matches the class name in the script below)
Overrides: Login (Note: The value here must match the name of the ‘Login’ Installation Exit exactly)
Active: True
Script:
var DomainAlertLogin = Class.create();
DomainAlertLogin.prototype = {
initialize : function() {
},
process : function() {
// the request is passed in as a global
var userName = request.getParameter("user_name");
var userPassword = request.getParameter("user_password");
if (typeof GlideUser != 'undefined')
var user = GlideUser;
else
var user = Packages.com.glide.sys.User;
var authed = user.authenticate(userName, userPassword);
if (authed)
return user.getUser(userName);
this.loginFailed();
//See if the userName given has any backslash characters included
if(userName.indexOf('\') > -1){
gs.addErrorMessage('Please remove any backslashes in username for login.');
}
return "login.failed";
},
loginFailed : function() {
var sysMessage = Packages.com.glide.ui.SysMessage;
var message = sysMessage.format("login_invalid");
var session = Packages.com.glide.sys.GlideSession.get();
session.addErrorMessage(message);
var userName = request.getParameter("user_name");
var EventManager = Packages.com.glide.policy.EventManager;
var t = Packages.com.glide.sys.Transaction.get();
EventManager.queue("login.failed", "", userName, t == null ? null : t.getRemoteAddr());
}
loginFailed : function() {
if (typeof GlideSysMessage != 'undefined')
var sysMessage = GlideSysMessage;
else
var sysMessage = Packages.com.glide.ui.SysMessage;
var message = sysMessage.format("login_invalid");
if (typeof GlideSession != 'undefined')
var session = GlideSession.get();
else
var session = Packages.com.glide.sys.GlideSession.get();
session.addErrorMessage(message);
var userName = request.getParameter("user_name");
if (typeof GlideEventManager != 'undefined')
var EventManager = GlideEventManager;
else
var EventManager = Packages.com.glide.policy.EventManager;
if (typeof GlideTransaction != 'undefined')
var t = GlideTransaction.get();
else
var t = Packages.com.glide.sys.Transaction.get();
EventManager.queue("login.failed", "", userName, t == null ? null : t.getRemoteAddr());
}
}
Great Post. Thanks. Very Useful.
Hi Mark,
great post, and just the kind of question that we are increasingly being asked. Another situation that I had recently was that a customer wanted to be able to prevent non-admin users from logging in to the system while the admins were performing some kind of maintenance on the instance – maybe applying some update sets from a Dev instance. I used the login Installation Exit to do this – admittedly I hacked some code I found on the forums, but hey, it works (it was probably your code anyway). It involves creating a new property in the sys_properties table (called tu.under.maintenance in my example), then testing that in the login script:
var TULogin = Class.create();
TULogin.prototype = {
initialize : function() {
},
process : function() {
// the request is passed in as a global
var userName = request.getParameter('user_name');
var userPassword = request.getParameter('user_password');
var user = Packages.com.glide.sys.User;
var authed = user.authenticate(userName, userPassword);
if (!authed) {
this.loginFailed();
return 'login.failed';
}
var isAdmin = false;
var rec = new GlideRecord('sys_user');
rec.addQuery('user_name', userName);
rec.query();
if(rec.next()){
//Query the roles table for this user
var rec1 = new GlideRecord('sys_user_has_role');
rec1.addQuery('user', rec.sys_id);
rec1.query();
while(rec1.next()){
if(rec1.role.getDisplayValue() == 'admin'){
isAdmin = true;
break;
}
}
}
var maint = gs.getProperty('tu.under.maintenance');
if ((!isAdmin && maint == 'true') && (userName.indexOf('@snc') == -1)) {
this.underMaint();
return 'login.failed';
}
return user.getUser(userName);
},
loginFailed : function() {
var sysMessage = Packages.com.glide.ui.SysMessage;
var message = sysMessage.format('login_invalid');
var GlideSession = Packages.com.glide.sys.GlideSession.get();
GlideSession.addErrorMessage(message);
var userName = request.getParameter('user_name');
var EventManager = Packages.com.glide.policy.EventManager;
EventManager.queue('login.failed', '', userName, '');
},
underMaint : function() {
var GlideSession = Packages.com.glide.sys.GlideSession.get();
GlideSession.addErrorMessage('System under maintenance - please try later');
var userName = request.getParameter('user_name');
var EventManager = Packages.com.glide.policy.EventManager;
EventManager.queue('login.failed', '', userName, '');
}
};
Thanks for sharing this. I should probably write sometime about creating, accessing, and displaying a system property in a custom properties page. Your example here is perfect. Adding a ‘gs.getProperty’ check is a great way to make this solution even easier for people to use and understand.
It took me a while, but here’s the post I promised about System Properties… https://servicenowguru.wpengine.com/system-definition/w…
I just came across a need for this. It works great. Very Nice!
Awesome. I’m glad it worked for you. Thanks for the feedback!
Excellent, I was just about to scroll through the list of user and de-activate them. I used the Installation exit script, which works fine, but the syntax checker tells me “WARNING at line 60: Missing semicolon.” Not sure why it asks me to put one there, but it doens’t seem to stop the script from working. Cheers…Mark
Glad to hear it works for you Mark. I’m not sure why you’re getting that error either. I don’t see it in my testing on the ServiceNow demo instance so it must just be some quirk with copy/paste or the syntax editor.
When I tried to paste the code in, I got syntax errors. I don’t know enough about coding to debug though.
WARNING at line 15: ‘user’ is already defined.
WARNING at line 35: ‘user’ used out of scope.
WARNING at line 39: ‘user’ used out of scope.
WARNING at line 56: ‘sysMessage’ is already defined.
WARNING at line 57: ‘sysMessage’ used out of scope.
WARNING at line 62: ‘session’ is already defined.
WARNING at line 63: ‘session’ used out of scope.
WARNING at line 70: ‘EventManager’ is already defined.
WARNING at line 75: ‘t’ is already defined.
WARNING at line 76: ‘EventManager’ used out of scope.
WARNING at line 78: Missing semicolon.
These warnings aren’t anything to worry about, the code here is 95% based on the out-of-box ServiceNow installation exits so they’re the ones responsible for the warnings :). I opted to keep it as similar as possible to the out-of-box code for consistency. It should run just fine though.
OK thanks!
I think this functionality should come out the box! It’s a great bit of functionality, very useful
This was very helpful!