Brian McMinn1
Giga Contributor

Currently in the Madrid and New York release it not possible to authenticate users with Oauth 2.0. Multi SSO provides 3 types of authentication, SAML 2.0, Digest Token and Basic Authentication.

My company uses an Oauth 2.0 solution with bearer token to authenticate our users into our applications. ServiceNow has Oauth for non interactive sessions and API calls, but not for interactive where a user may be using things like a Service Portal, Chat, Request Catalog etc.

There is a workaround to authenticate with a Oauth 2.0 solution using many ootb features that ServiceNow provides. 

1. Scripted Rest API's

2. Outbound Rest Messages

3. Script Includes

In order to set up your Oauth 2.0 solution first you will need a few things from the Oauth Provider.

1. Client Secret

2. Grant Code

3. Oauth 2.0 URL

 

Now come the steps of setting up the flow to get your users authenticated in ServiceNow.

1. Create a Scripted Rest API with no authentication. We will use this as a pass through and the redirect url for the Oauth 2.0 solution.

2. Ensure if you are passing more than JSON, under content negotiation check "Override Supported Response Formats"

find_real_file.png

3. Create a "GET" Resource for the Scripted Rest API

4. Use the resource URL as the redirect for the SSO

5. Once the redirect is redirected to the Scripted REST API, grab the grant token from the headers and create a grant code variable

(function process(/*RESTAPIRequest*/ request, /*RESTAPIResponse*/ response) {
   //pass in the grant code
   var queryParams = request.queryParams; 
   var code = queryParams.code;
   

6. Next make a post call back to the SSO Server with Grant Code to get the bearer token.

 //make a post call with the grant code to get the bearer token
   var r = new sn_ws.RESTMessageV2('sso', 'post');
   r.setStringParameterNoEscape('code',code);
   var apiResponse = r.execute(); 
   var responseBody = apiResponse.getBody();
   var httpStatus = apiResponse.getStatusCode();
   var parse = JSON.parse(responseBody);
   var bearer = parse.access_token;

 7. Set a variable of bearer before making an outbound REST post back to the SSO server with the bearer token to get the user profile from the SSO application. I created a script include to make this next rest call called DigestSSO. We are now going to use the information we get from Oauth 2.0 and convert that into a digest method of SSO.

 

var DigestSSO = Class.create();
var user;
DigestSSO.prototype = {
    initialize: function() {
    },
	getProfile: function(bearer) {
	 var r = new sn_ws.RESTMessageV2('sso', 'getProfile');
             r.setStringParameterNoEscape('token',bearer);
         var response = r.execute();
         var responseBody = response.getBody();
         var httpStatus = response.getStatusCode();
         var userProfile = JSON.parse(responseBody);
	 user = userProfile;
	},

 8. I make another POST call back to the SSO server and pass in the bearer token from the Scripted Rest API. I also created a global variable in this Script Include to capture my user information to be used throughout the script include for GlideRecord queries. Once I parse the JSON, I have my JSON information for the user from the Oauth 2.0 SSO.

 

9. Next, I use simple GlideRecords to check if the user is in the system. If not, I create a profile and in our case this was for one specific group so I added them to a group.

 

	isUserExist: function() {
		if(!user.email) {
			gs.addInfoMessage("This User Does Not Exist in our network");
		}
		var contact = new GlideRecord('customer_contact');
                    contact.addQuery('email',user.email);
                    contact.query();
                     if (!contact.next()) { 
	      // if query doesnt return a match, create account, if we do see an user we do              nothing
			contact.email = user.email;
			contact.user_name = user.email;
			contact.first_name = user.first_name;
			contact.last_name = user.last_name;	
			contact.u_customer_type = "consumer";
			contact.insert();
			//add new user to group
		    var rec = new GlideRecord('sys_user');
			        rec.addQuery('user_name', contact.user_name);
				rec.query();
				while(rec.next()){
			    //Create a new group relationship record for this user
				var rec1 = new GlideRecord('sys_user_grmember');
					rec1.initialize();
					rec1.user = contact.sys_id;
					rec1.group.setDisplayValue('a group');
				    rec1.insert();
				}
		 
		 }
	},

 

9. Now I go back to Scripted Rest API and call a method to get the user back in the API.

  //use the bearer token to get the profile of the user//
   var digest = new DigestSSO();
       digest.getProfile(bearer);
	//check the user
       digest.isUserExist();
   var username = digest.getUser();
	

10. I grab the username of the new user and get ready to do a Digest SSO in ServiceNow which is supported. 

 

11. First, I Create a new Identity Provider by navigating to Multi-SSO -> Identity Providers

12. I choose a "Digest" Method and am presented with a form.

Fill out the Name of the Digest Method, the user field you want the digest to check against, the header SE_USER which will be the username of the user you are trying to digest, DE USER which will be the digest token, a secret passphrase and then use the MultiSSO_DigestToken single sign on script to ultimately digest your user.

find_real_file.png

13. IMPORTANT = for the field DE_USER the Digest Token needs to be generated by the SSO provider and sent with the JSON file from the user profile. There are a few ways to create a digest token that will match with whats in ServiceNow by using ServiceNow's hashing method in the MutiSSO_DigestedToken script include and the secret passphrase.

 

14. A few methods of hashing from the SSO side are:

 Here is a sample in C:

  private stringdigestData(string strEncryptionMethod , string message , string sharedKey ) {
            UnicodeEncoding myUnicodeEncoding  = new UnicodeEncoding ( ) ;
 
            byte [ ] messageBytes  = System. Text. Encoding. ASCII. GetBytes (message ) ;
            byte [ ] sharedKeyBytes  = System. Text. Encoding. ASCII. GetBytes (sharedKey ) ;
            byte [ ] hashedMessage ;
 
            string b64SHA1Message ;
 
             if (this. DEBUG ) {
                TextBoxMessage. Text = message ;
                TextBoxSecret. Text = sharedKey ; }
 
             switch ( (strEncryptionMethod ) )
 
             { case "SHA1" :
 
                    HMACSHA1 hmacsha1  = new HMACSHA1 ( ) ;
                    hmacsha1. Key = sharedKeyBytes ;
                    hashedMessage  = hmacsha1. ComputeHash (messageBytes ) ;
                    b64SHA1Message  = Convert. ToBase64String (hashedMessage ) ; if (this. DEBUG ) TextBoxDigest. Text = Convert. ToString (hashedMessage ) ; break ;
 
                 case "MD5" :
 
                    HMACMD5 hmacmd5  = new HMACMD5 (sharedKeyBytes ) ;
                    hashedMessage  = hmacmd5. ComputeHash (messageBytes ) ;
                    b64SHA1Message  = Convert. ToBase64String (hashedMessage ) ; if (this. DEBUG ) TextBoxDigest. Text = Convert. ToString (hashedMessage ) ; break ;
 
                 default :
 
                    b64SHA1Message  = "Unknown Encryption Method" ; break ;
 
             }
 
 
            TextBoxBase64. Text = b64SHA1Message ; return b64SHA1Message ;
 
         }

 

And Another in Java:

import javax.crypto.Mac ; import javax.crypto.spec.SecretKeySpec ; import sun.misc.BASE64Encoder ; // public class DigestTest  { private static final String MAC_ALG  = "HmacSHA1" ; // default to something JDK 1.4 has String fKey  = "abc123" ; public byte [ ] getDigest ( String acct ) { try { byte [ ] bkey  = fKey. getBytes ( ) ; byte [ ] data  = acct. getBytes ( ) ;
           Mac mac  = null ; try {
               mac  = Mac. getInstance (MAC_ALG ) ;
               mac. init ( new SecretKeySpec (bkey, MAC_ALG ) ) ; } catch ( Exception e ) {
               e. printStackTrace ( ) ; } byte [ ] sig  = mac. doFinal (data ) ; String signature  = new String (sig ) ; System. out. println ( "value:" + acct ) ; System. out. println ( "digested value:  " + signature ) ; return sig ; } catch ( IllegalStateException e ) {
       	e. printStackTrace ( ) ; } return null ; } public static void main ( String [ ] args ) {
   	BASE64Encoder encoder  = new BASE64Encoder ( ) ;
   	DigestTest test  = new DigestTest ( ) ; String userName  = "user_name" ; System. out. println ( "base 64 digest username: " + encoder. encode (test. getDigest (userName ) ) ) ; } }

15. Once you implement you Digest token method on the SSO application side, send the Digest code as part of the JSON Object when you grab the User Profile in Step 7.

 

16. Next in the Scripted Rest API run the following code:

//authenticate user
           var url = 'https://<YOUR INSTANCE>.service-now.com/login_with_sso.do?glide_sso_id=dbf05e43db3b3b007de517e15b96195d&SE_USER=' + username + ' &DE_USER= + digestcode
	   response.setStatus(301);
           response.setLocation(url);

 

17. Next the exciting part, set your digest URL, set a 301 status and inject the URL into ServiceNow. You will have now completed a interactive session in ServiceNow using Oauth 2.0 with bearer token. Hope this helps and reach out with any questions

Version history
Last update:
‎09-16-2019 08:32 PM
Updated by: