Assistance with JWT generation with Grouper integration

scoburn1
Tera Contributor

Good Morning All,

 

We have been asked to attempt to integrate with the following application (Grouper) here:

https://spaces.at.internet2.edu/display/Grouper/Grouper+web+services+-+authentication+-+self-service...

 

The authentication method available is "Self Service JWT". Their team has provided us the Private Key string outlined in Step 1. Now, we are attempting to generate the JWT using this on our end. 

 

While the following script looks to output a JWT, we are unclear if this is created and signed properly or what mechanisms ServiceNow has to handle these. We did see the documentation here (https://docs.servicenow.com/bundle/tokyo-platform-security/page/administer/security/concept/Scoped-A...) but as we are not setting them up as an Oauth provider I don't believe this is the path forward. There are also some signing references on the CertificateEncryption() API but again this looks to point to certificate records whereas we are just being provided a Key. We are also not using a MID server or importing external libraries at this time. 

Here is the current Script Include and client call used:

var JWTGenerator = Class.create(); 

JWTGenerator.prototype = { 

    initialize: function() {}, 

    generateJWT: function() { 

        try { 

            // Define the JWT Header 

            var header = { 

                alg: "RS256", 

                typ: "JWT" 

            }; 


            // Define the JWT Payload 

            var payload = { 

                iss: "your-issuer", 

                sub: "your-subject", 

                aud: "your-audience", 

                exp: (new Date().getTime() / 1000) + 600,  // Token expiration time (10 minutes from now) 

                iat: new Date().getTime() / 1000, 

                // Add additional claims here as needed 

            }; 

            // Convert the header and payload to Base64 

            var encodedHeader = GlideStringUtil.base64Encode(JSON.stringify(header)); 

            var encodedPayload = GlideStringUtil.base64Encode(JSON.stringify(payload)); 

            encodedHeader.GlideStringUtil.replace(/=+$/, ''); //strips out == padding  

            //encodedPayload.GlideStringUtil.replace(/=+$/, ''); 

            encodedPayload.replaceAll("(\=+$\)", ""); 

            gs.info("Payload: " + encodedPayload); 

            var newb64EncodedText = encodedPayload.replaceAll("(\=+$\)", ""); 

            gs.info("Java Payload: " + newb64EncodedText); 

                 

 
            // Create the signature 

            var key = gs.getProperty('grouper_dev_key');
			gs.info("Key is: " + key);

			var dataToSign = encodedHeader + "." + newb64EncodedText;

            var signature = GlideStringUtil.base64Encode( 

                GlideDigest.generate('SHA256', dataToSign, key) 

            ); 

 

            // Return the complete JWT 

            var jwtuser = 'jwtUser_OWVmNzE4M2M3MGU4NDNkYmEwNTdiYzk3N2Q2ZTRjZDM=_';

            return jwtuser + encodedHeader + "." + newb64EncodedText + "." + signature; 

        } catch (e) { 

            gs.error("Error generating JWT: " + e.message); 

            return null; 

        } 

    }, 

 
 

    type: 'JWTGenerator' 

}; 

 

 

Calling from a Background Script:

var jwtGen = new JWTGenerator(); 
var jwt = jwtGen.generateJWT(); 
gs.info("Generated JWT: " + jwt); 

 

Any assistance or ideas would be greatly appreciated.

 

Thank you.

7 REPLIES 7

For this step 2. Create a JWT Signing key and assign corresponding signing jks:
That Signing Key Password field would store the Grouper private key in this case?

same as the key store pwd, the grouper admin who generated the file for you should provide it too

you use the same for the Signing key

 

SNEmy_0-1748030141229.png

 



For step 4. Create a 3rd party Oauth provider in Application Registry. Configure JWT Provider and Signing Keys in ServiceNow:

use this documentation if they dont use OAuth2.0, you dont need step 4

use this to generate your token from a fixe script 

 

var bearerPrefix = "Bearer jwtUser_XXXXXXXX=_";
var jwtAPI = new sn_auth.GlideJWTAPI();
var headerJSON = { typ: "JWT", alg: "RS256" };
var header = JSON.stringify(headerJSON);

var gdt = new GlideDateTime();
var iat = Math.floor(gs.nowDateTime().getNumericValue() / 1000);
var exp = Math.floor(gdt.getNumericValue() / 1000);

// NOTE: Replace the `iss` value with your application ID if needed
var payloadJSON = {
"iat": iat,
"exp": exp,
"iss": ""
};
var payload = JSON.stringify(payloadJSON);
 
let me know if you still need help with this.
Thanks,
Imane

var jwtProviderSysId = "jwtProvider_sys_id";
var jwt = jwtAPI.generateJWT(jwtProviderSysId, header, payload);

print( bearerPrefix + jwt);

SN Emy
Tera Guru

Go here to find examples of the JSON requests

$body = @"
{
"WsRestGroupSaveRequest":{
"wsGroupToSaves":[
{
"wsGroup":{
"extension":"test_cloud_group",
"description":"desc1",
"displayExtension":"test_cloud_group",
"name":"test:AutomationTest:test_cloud_group"
},
"wsGroupLookup":{
"groupName":"test:AutomationTest:test_cloud_group"
}
}
]
}
"@

$body = @"
{
"WsRestAddMemberRequest":{
"subjectLookups":[
{
"subjectSourceId":"Uperson",
"subjectId":"user_name"
},
{
"subjectSourceId":"UOFUperson",
"subjectId":"user_name"
},
{
"subjectSourceId":"UOFUperson",
"subjectId":"user_name"
},
{
"subjectSourceId":"FUperson",
"subjectId":"user_name"
}
]
,
"wsGroupLookup":{
"groupName":"test:AutomationTest:test_cloud_group"
}
}
}
"@

$body = @"
{
"WsRestAssignGrouperPrivilegesRequest":{
"allowed":"T",
"privilegeNames":[
"update",
"read"
]
,
"wsSubjectLookups":[
{
"subjectSourceId":"UOFUperson",
"subjectId":"user_name"
},
{
"subjectSourceId":"UOFUperson",
"subjectId":"user_name"
},
{
"subjectSourceId":"UOFUperson",
"subjectId":"user_name"
}
]
,
"wsGroupLookup":{
"groupName":"test:AutomationTest:test_cloud_group"
},
"privilegeType":"access"
}
}
"@

https://www.servicenow.com/docs/bundle/xanadu-api-reference/page/app-store/dev_portal/API_reference/NotifySMS/concept/NotifySMSAPI.html

scoburn1
Tera Contributor

Hi Emy,

 

I just wanted to provide a current update after going through these steps. This method has so far provided a signed valid JWT - we are however still receiving 401 Unauthorized 'The request has not been applied to the target resource because it lacks valid authentication credentials for that resource.' errors when attempting to call a known good endpoint. 

I'll list our steps just to be sure we didn't miss anything:

1. Using the documentation provided (https://support.servicenow.com/kb?id=kb_article_view&sysparm_article=KB0717946) we created a self signed certificate. We did not change anything about the provided instructions below beyond inserting the key provided to us from Grouper into the certificate:
"Upload jks which has jwt signing key in sys certificate.Java keystore with a signing key (Private-Public key pair) is generated by customer. Use the below command in the terminal to generate a java keystore with a self signed certificate."
keytool -genkey -alias snclient -keyalg RSA -validity 365 -keystore jwtdemo.keystore -storepass [GROUPERPRIVATE KEY] -keypass [GROUPER PRIVATE KEY]certificate.png

2. As this presented as a working valid keystore, we set up the JWT Keys as per the instructions:

jwt keys.png

 

3. Lastly we pointed the JWT Provider at this key. No particular claims were specified or provided but here is a shot of that configuration screen:

jwtprovider.png

4. The next steps in (https://support.servicenow.com/kb?id=kb_article_view&sysparm_article=KB0717946) do not look to apply since there is no oauth provider. Next we use this script to generate the token. This points at the JWT Provider we just made:

var bearerPrefix = "Bearer jwtUser_XXXX"; //the user. we replace this with the full jwtUser string
var jwtAPI = new sn_auth.GlideJWTAPI();
var headerJSON = { typ: "JWT", alg: "RS256" };
var header = JSON.stringify(headerJSON);
var gdt = new GlideDateTime();
var iat = Math.floor(gs.nowDateTime().getNumericValue() / 1000);
var exp = Math.floor(gdt.getNumericValue() / 1000);
// NOTE: Replace the `iss` value with your application ID if needed
var payloadJSON = {
"iat": iat,
"exp": exp,
"iss": ""
};
var payload = JSON.stringify(payloadJSON);
var jwtProviderSysId = "656b26d293aaa210cfd8f5bcb903d65a"; //jwt provider sys_id
var jwt = jwtAPI.generateJWT(jwtProviderSysId, header, payload);
 gs.info("JWT: " + bearerPrefix + jwt);

  

This generates a JWT for us successfully:

successful JWT generation.png

And I can verify that this is a valid signed JWT on jwt.io. I can verify that modified claims come over, and the expiration sets correctly so it does look to be generating the correct information. I exported the public key for verification from the certificate created in Step 1 using: 

keytool -exportcert -alias snclient -keystore C:/XXXX/XXXX/OneDrive/Desktop/jwtdemo.keystore -rfc -file C:/Users/XXXX/XXXX/Desktop/snclient-cert.pem

jwtioresult.png

Finally, we attempt to use this in our test REST call against the known good endpoint:

authorization.png

 

Here we receive the 401 errors regarding authorization. I wanted to list all my steps clearly to see if there was anything clearly incorrect on our end or if you had any further insight.

 

Thanks so much,

Seth