Having difficulty integrating Azure DevOps REST API with ServiceNow, attempting to use refresh token to gain a new access token
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
‎06-15-2021 12:54 AM
I have managed to setup some connectivity between ServiceNow and Azure DevOps. Here is the quick summary of what I've done:
- Setup a third party OAuth provider in System oAuth - Application Registry
- Set the Default Grant Type of Authorization Code, scopes and a customised oAuthUtil Script (needed this to get the oAuth Token link to work when setting up the REST Message
- Default Profile has the correct entity scope set on it too.
- Setup a System Web Services - REST Message thus:
Get oAuth Token link works here, puts a refresh token (times out in 100 days time) and an access token (times out in 30 minutes) into System oAuth - Manage Tokens. The test link then works for the 30 minutes the access token is active for.
Here's the problem, after 30 min when I click the test link, I get an error "User Not Authenticated. Could not retrieve a new access token with the refresh token. invalid_request, Missing parameters: access_token"
After having watched Josh Nerius's Live Coding youtubes 2017 around oAuth, it looks as though on every invokation of the test, the access token should be refreshed by using the refresh token and I can't get ServiceNow to replicate this behaviour. (The main difference between Azure DevOps REST API and Office 365 appears to be no "offline" capability.
If I click on Link "Get oAuth Token" I can manually refresh tokens and the REST calls work for another 30 minutes.
I'm thinking that I need to include something specific in the request body as the message is sent out to Azure DevOps as per the Azure DevOps authentication here:
...but not sure how to amend the interceptRequestParameters function within (I can add URI parameters and have got all of the other functions work as I think they should be.
OAuthUtilAF3 (latest attempt)-
//* Dont edit this script include. Best practise: Extend this script include and override the functions.
var OAuthUtilAF3 = Class.create();
OAuthUtilAF3.prototype = {
initialize: function(oauthContext) {
this.oauthContext = oauthContext;
},
interceptRequestParameters : function(requestParamMap) {
// Add/Modify request parameters if needed
gs.info(Date.now() + " 1");
var profGr = this.oauthContext.getOAuthProfile();
gs.info(Date.now() + " 1.15 " + profGr.getValue("grant_type"));
//var bodyContent = "client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer&client_assertion={0}&grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer&assertion={1}&redirect_uri={2}";
requestParamMap.put("grant_type", "urn:ietf:params:oauth:grant-type:jwt-bearer");
requestParamMap.put("client_assertion_type","urn:ietf:params:oauth:client-assertion-type:jwt-bearer");
requestParamMap.put("access_token", "INSERT_CLIENT SECRET HERE");
requestParamMap.put("assertion",requestParamMap.get('code'));
gs.info(Date.now() + " 1.25 " + requestParamMap.get("code") );
gs.info(Date.now() + " 1.5" );
this.preprocessAccessToken(requestParamMap);
},
parseTokenResponse: function(accessTokenResponse) {
gs.info(Date.now() + " 2");
this.postprocessAccessToken(accessTokenResponse);
},
preprocessAuthCode: function(requestParamMap) {
gs.info(Date.now() + " 3");
requestParamMap.put('access_type','offline');
gs.info(Date.now() + " 3.5");
},
preprocessAccessToken: function(requestParamMap) {
gs.info(Date.now() + " 4");
requestParamMap.put("grant_type", "urn:ietf:params:oauth:grant-type:jwt-bearer");
requestParamMap.put("client_assertion_type","urn:ietf:params:oauth:client-assertion-type:jwt-bearer");
requestParamMap.put("client_assertion", "INSERT_CLIENT_HERE");
requestParamMap.put("assertion",requestParamMap.get('code'));
gs.info(Date.now() + " 4.25 code " + requestParamMap.get('code'));
gs.info(Date.now() + " 4.5");
},
postprocessAccessToken: function(accessTokenResponse) {
gs.info(Date.now() + " 5");
var contentType = accessTokenResponse.getContentType();
var contentBody = accessTokenResponse.getBody();
var paramMap = accessTokenResponse.getparameters();
gs.info(Date.now() + " 5.15 cb " + contentBody);
gs.info(Date.now() + " 5.15 ct " + contentType);
gs.info(Date.now() + " 5.15 pm " + JSON.stringify(paramMap));
if (contentType && contentType.indexOf('application/json') != -1) {
gs.info(Date.now() + " 5.25 " + JSON.stringify(accessTokenResponse.getBody()));
var tokenResponse = (new global.JSON()).decode(accessTokenResponse.getBody());
//var paramMap = accessTokenResponse.getparameters();
paramMap.put("access_token", tokenResponse["access_token"].toString());
paramMap.put("refresh_token", tokenResponse["refresh_token"].toString());
for (param in tokenResponse)
gs.info(Date.now() + " 5.5 bearer " + param + " " + tokenResponse[param].toString());
//paramMap.put(param, tokenResponse[param].toString());
}
},
type: 'OAuthUtilAF3'
};
I want to be able to either use the demonstrated refresh capability *or* have a scheduled job in the background effectively click that link every 29 minutes. Anyone seen similar or know of the code amendment I can do?
- Labels:
-
Integrations

- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
‎06-15-2021 01:07 PM
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
‎03-12-2024 11:09 AM
Iam implementing integration between servicenow and azure devops using oauth 2.0 in outbound rest message in servicenow,when incident will be create incident table need to be created in workitem in azure devops. when i createdg application registration type is Connect to a third party OAuth Provider , its asking authorization URL , my questions is what values...authorization URL and URL token field.please refer screen shot .
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
‎06-15-2021 02:32 PM
Thanks for that Thomas, unfortunately, been through that process a fair few times now (and it is how I initially set all the manual oauth token fetching up. But due to the way MS has setup their end of the REST API connection, I need to customise requests going outbound to Azure DevOps.
I'm in agreement there with you there and working on fixing the fact the access token isn't refreshing.
Just found this:
https://community.servicenow.com/community?id=community_question&sys_id=0dcf5c9d1b4b8890ada243f6fe4bcb39
which has given me a clue on the contents of the requestParamMap in the interceptRequestParameters functions. I just need to work out how to send this in the content to Azure DevOps in that part of the request (I can now fill in the 0,1 and 2 inputs from the contents in the requestParamMap)
client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer&client_assertion={0}&grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer&assertion={1}&redirect_uri={2}
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
‎03-29-2022 02:19 AM
You almost had it: your code lacks the refresh token management.
var OAuthUtilAF3 = Class.create();
OAuthUtilAF3.prototype = {
initialize: function(oauthContext) {
this._gsinfo("BEGIN initialize");
this.oauthContext = oauthContext;
this._gsinfo("Init1: " + oauthContext.constructor.name);
this._logEnabled = gs.getProperty("OAuthUtilAF3.log_enabled","false");
},
interceptRequestParameters: function(requestParamMap) {
// Add/Modify request parameters if needed
this._gsinfo("BEGIN interceptRequestParameters");
this._dumpParamMap("1.15", requestParamMap);
this.modifyRequestParamMap(requestParamMap);
this._dumpParamMap("1.30", requestParamMap);
this.preprocessAccessToken(requestParamMap);
},
modifyRequestParamMap: function(requestParamMap) {
var profGr = this.oauthContext.getOAuthProfile();
var redirectUrl = profGr.oauth_entity.getRefRecord().redirect_url;
var azureSecret = profGr.oauth_entity.getRefRecord().u_azure_client_secret.toString();
requestParamMap.put("client_assertion_type", "urn:ietf:params:oauth:client-assertion-type:jwt-bearer");
requestParamMap.put("client_assertion", azureSecret);
if (requestParamMap.get("refresh_token")) {
requestParamMap.put("grant_type", "refresh_token");
requestParamMap.put("assertion", requestParamMap.get("refresh_token"));
} else {
requestParamMap.put("grant_type", "urn:ietf:params:oauth:grant-type:jwt-bearer"); // grant type = jwt-bearer
requestParamMap.put("assertion", requestParamMap.get("code"));
}
requestParamMap.put("redirect_uri", redirectUrl);
},
preprocessAccessToken: function(requestParamMap) {
this._gsinfo("BEGIN preprocessAccessToken");
this._dumpParamMap("2.15", requestParamMap);
this.modifyRequestParamMap(requestParamMap);
this._dumpParamMap("2.30", requestParamMap);
},
parseTokenResponse: function(accessTokenResponse) {
this._gsinfo("BEGIN parseTokenResponse");
this._gsinfo("3.26: accessTokenResponse=" + accessTokenResponse.constructor.name);
this._gsinfo("3.27: accessTokenResponse.toString()=" + accessTokenResponse.toString());
this.postprocessAccessToken(accessTokenResponse);
},
postprocessAccessToken: function(accessTokenResponse) {
this._gsinfo("BEGIN postprocessAccessToken");
var contentType = accessTokenResponse.getContentType();
var contentBody = accessTokenResponse.getBody();
var paramMap = accessTokenResponse.getparameters();
this._gsinfo("postprocessAccessToken: contentBody=" + contentBody);
this._gsinfo("postprocessAccessToken: contentType=" + contentType);
this._gsinfo("postprocessAccessToken: JSON.stringify(paramMap)=" + JSON.stringify(paramMap));
if (contentType && contentType.indexOf('application/json') != -1) {
this._gsinfo("5.25 " + JSON.stringify(accessTokenResponse.getBody()));
var tokenResponse = (new global.JSON()).decode(accessTokenResponse.getBody());
//var paramMap = accessTokenResponse.getparameters();
paramMap.put("access_token", tokenResponse["access_token"].toString());
paramMap.put("refresh_token", tokenResponse["refresh_token"].toString());
for (param in tokenResponse)
this._gsinfo("5.5 bearer " + param + " " + tokenResponse[param].toString());
//paramMap.put(param, tokenResponse[param].toString());
}
},
preprocessAuthCode: function(requestParamMap) {
this._gsinfo("BEGIN preprocessAuthCode");
//requestParamMap.put('access_type','offline');
this._dumpParamMap("6.30", requestParamMap);
},
_gsinfo: function(text) {
if (this._logEnabled)
gs.info("OAuthUtilAF3 " + Date.now() + " | " + text);
},
_dumpParamMap: function(funcName, requestParamMap) {
var strKeys = '' + requestParamMap.getKeys();
if (strKeys) {
var keys = strKeys.split(',');
for (var i = 0; i < keys.length; i++) {
this._gsinfo(funcName + " param=" + keys[i] + ': ' + requestParamMap.get(keys[i]));
//requestParamMap.put(keys[i],value);
}
}
},
type: 'OAuthUtilAF3'
};
Add this script https://support.servicenow.com/kb?id=kb_article_view&sysparm_article=KB0823628 from ServiceNow and you should be happy.