Custom Login Validation with Installation Exits

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.

AdminOnlyLogin Installation Exit
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:

gs.include("PrototypeServer");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.

DomainAlertLogin Installation Exit
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:

gs.include("PrototypeServer");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());
}
}

Date Posted:

September 28, 2010

Share This:

13 Comments

  1. Steve Darity September 28, 2010 at 7:33 am

    Great Post. Thanks. Very Useful.

  2. Brian Broadhurst September 29, 2010 at 1:55 am

    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:

     
    gs.include('PrototypeServer');
     
    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, '');
     
           }
     
    };
     
    
    • Mark Stanger September 29, 2010 at 2:14 am

      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.

    • Mark Stanger October 28, 2010 at 5:36 am

      It took me a while, but here’s the post I promised about System Properties… https://servicenowguru.com/system-definition/w

  3. Michael Brown July 2, 2012 at 3:08 pm

    I just came across a need for this. It works great. Very Nice!

    • Mark Stanger July 2, 2012 at 4:31 pm

      Awesome. I’m glad it worked for you. Thanks for the feedback!

  4. Mark Sandner January 29, 2013 at 11:02 am

    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

    • Mark Stanger January 29, 2013 at 1:17 pm

      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.

  5. Shannon August 28, 2015 at 7:58 am

    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.

    • Mark Stanger August 28, 2015 at 8:46 am

      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.

      • Shannon August 28, 2015 at 9:08 am

        OK thanks!

  6. Ahmed April 17, 2017 at 7:39 am

    I think this functionality should come out the box! It’s a great bit of functionality, very useful

  7. Melanie December 14, 2017 at 3:29 pm

    This was very helpful!

Comments are closed.

Categories

Tags

Loading

Fresh Content
Direct to Your Inbox

Just add your email and hit subscribe to stay informed.