Get a first look at what's coming. The Developer Passport Australia Release Preview kicks off March 12. Dive in! 

Custom HTTP REST API Discovery with Two-Stage Authentication

Matt Bowler
Tera Contributor

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?

 

https://docs.servicenow.com/bundle/vancouver-it-operations-management/page/product/discovery/task/ru...

 

Thanks,

Matt

1 REPLY 1

hcallen
Tera Contributor

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);
}