Custom HTTP REST API Discovery with Two-Stage Authentication
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
12-27-2023 12:15 PM - edited 12-29-2023 12:02 PM
Hello,
I am attempting to produce a custom discovery process which obtains data through a REST API. The documentation via the link below is fairly helpful, but it assumed a single set of authentication credentials are provided with each API call. In my use case, the REST API first requires the client to authenticate via a Login endpoint and obtain an access token, which is then inserted into the Authorization header of subsequent calls. Is it possible for ServiceNow's discovery module to accommodate these requirements?
Thanks,
Matt
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
3 weeks ago - last edited Friday
After reading through a handful of OOTB custom HTTP patterns and mid server script includes, I have something that's working enough for me. I wish the Java packages leveraged had documentation and there was a documented method that reliably knew which credentials to use instead of looping through all of them for a match.
/**
* Discovery Probe - API Endpoint Query
*
* Authenticates against a management API using basic auth credentials,
* obtains a short-lived API key, then queries a target resource endpoint.
*
* Context variables:
* $username - Username to match in credential store
* $resourcePath - API resource path to query
* $outputAttribute - CTX attribute name for storing the response body
*/
var LOG_PREFIX = 'API Endpoint Probe';
var managementIp = CTX.getManagementIP();
// ── Packages & credential setup ────────────────────────────────────────
var credFactory = Packages.com.snc.commons.credentials.CredentialsProviderFactory;
var provider = credFactory.getCredentialsProvider();
var dbString = Packages.com.snc.automation_common.integration.creds.CredentialType;
var dbStringConv = [dbString.fromDbString("basic_auth")];
var base64 = new Packages.org.apache.commons.codec.binary.Base64();
var ioutils = new Packages.org.apache.commons.io.IOUtils();
var credentials = provider.iterator(dbStringConv, null, null, null);
// ── Step 1: Find matching credential ───────────────────────────────────
var username = '';
var password = '';
var credDetails = '';
while (credentials.hasNext()) {
credDetails = credentials.next();
username = credDetails.getAttribute("user_name");
if (username == $username) {
password = credDetails.getAttribute("password");
break;
}
}
if (!username || !password) {
ms.log(LOG_PREFIX + ' - No matching credential found for user: ' + $username);
CTX.setAttribute($outputAttribute, '');
} else {
// ── Step 2: Authenticate and obtain API key ────────────────────────
var apiKeyAuthValue = "Basic " + base64.encodeBase64String(ioutils.toByteArray(username + ':' + password));
var authUrl = 'https://' + managementIp + '/api/v1/realm_auth';
var authBody = JSON.stringify({"realm": "some-realm"});
var apiKey = '';
var authReq = new Packages.com.glide.communications.HTTPRequest(authUrl);
authReq.addHeader("Content-Type", "application/json");
authReq.addHeader("Authorization", apiKeyAuthValue);
var authResponse = authReq.post(authBody);
var authStatus = authResponse.getStatusCode();
var authResponseBody = authResponse.getBody();
if (authStatus < 200 || authStatus >= 300) {
ms.log(LOG_PREFIX + ' - Auth failed on ' + managementIp +
' - HTTP ' + authStatus);
} else if (!authResponseBody) {
ms.log(LOG_PREFIX + ' - Auth returned empty body on ' + managementIp);
} else {
try {
var apiKeyJson = JSON.parse(authResponseBody);
if ("api_key" in apiKeyJson) {
apiKey = apiKeyJson.api_key;
} else {
ms.log(LOG_PREFIX + ' - Auth response missing api_key field on ' + managementIp);
}
} catch (e) {
ms.log(LOG_PREFIX + ' - Failed to parse auth response on ' + managementIp +
': ' + e.message);
}
}
// ── Step 3: Query the target endpoint ──────────────────────────────
var respBody = '';
if (!apiKey) {
ms.log(LOG_PREFIX + ' - Skipping endpoint query, no API key obtained for ' + managementIp);
} else {
var endpointUrl = 'https://' + managementIp + $resourcePath;
var authValue = "Basic " + base64.encodeBase64String(ioutils.toByteArray(apiKey + ':'));
var endpointReq = new Packages.com.glide.communications.HTTPRequest(endpointUrl);
endpointReq.addHeader("Content-Type", "application/json");
endpointReq.addHeader("Authorization", authValue);
var endpointResp = endpointReq.get();
var endpointStatus = endpointResp.getStatusCode();
if (endpointStatus < 200 || endpointStatus >= 300) {
ms.log(LOG_PREFIX + ' - Endpoint query failed on ' + managementIp +
$resourcePath + ' - HTTP ' + endpointStatus);
} else {
respBody = endpointResp.getBody() || '';
}
}
CTX.setAttribute($outputAttribute, respBody);
}
