REST over NTLM - solution proposed

Jacob_Heubner
Mega Expert

After spending some time searching on the forums for something like this and coming up empty, I did some trial and error on my own and found something that works.  I don't know if others will find this useful, but after some internal demos to colleagues, they suggested I post on the forums.

So, as we know, setting up REST Messages to invoke external APIs via Basic Authentication or OAUTH is well-documented and well supported.  However, we had an integration in our environment that required NTLM authentication, which isn't natively supported in ServiceNow.

However, Flow Designer makes it very easy to create an Action that runs a Powershell Script and returns the results of that script to the Flow in the outputs, so, we took advantage of how easy it is to use NTLM authentication in Powershell and how easy it is to write Powershell from inside a ServiceNow Flow Action, and came up with something that has been working for us. You do need to have the Powershell plugin for the IntegrationHub for this to work.

Credentials

Though we're not using Basic Auth, NTLM authentication is similar in that it wants a username and password.  So, we started by creating a Basic Auth Credential (basic_auth_credentials) that provide the username and password we'll need to use to invoke our target API.  We don't want to create an alias, because we don't want to use these credentials to log into a target host - we're just trying to safely and securely store the credentials we're going to use later.

find_real_file.png

Flow Designer

Then, we need to create our new Action in Flow Designer.  We called ours 'NTLM REST via Powershell'

Inputs

We created input variables for the following parameters we will use in our Flow:

Label Name Type
midserver midserver Reference.Mid Server
basic_auth_credentials basic_auth_credentials Reference.Basic Auth Credentials
uri uri String
resource resource String
parameters parameters String

Powershell Step

Once you have the parameters configured, insert a Powershell step.

There are two sections in the Powershell Step - the first is Connection Details, and the second is Script Details

Connection Details
So, normally, the way this step would work, you'd configure a connection, and the midserver that gets selected would get used to connect to the Host specified in the connection details (either explicitly set, or determined by your credential alias), and then would try to run this Powershell script on THAT host.  We don't want to do that.  What we want to do is tell the system to run this Powershell script on OUR midserver; the _script_ is doing the work to connect to a target URI, so our connection parameters are really about making sure that the action does NOT try to connect to some other host to run.

Connection: Define connection inline
Credential Alias: <leave blank>
Host: 120.0.0.1
Port: <leave blank>
MID Selection: Specific MID Server
MID Server: action>midserver (using the input variable to determine the mid). You may not need this in your system.  We have certain midservers that can reach out to the internet, to hosts on our domains, or to other specific domains.  It was easier for us to manually choose  midserver that would always work, rather than trust the autoselection.
Remoting Type: Run on a MID Server or have your script establish a remote session

Script Details
Script Type: Inline Script
Input Variables -

Name Value
resource action>resource (input parameters)
user_name action>basic_auth_credentials>User name (input parameters)
pass_word action>basic_auth_credentials>Password (input parameters)
params action>parameters (input parameters)
uri action>uri (input parameters

Command -
This is the Powershell script that does the real work, just using the parameters we're passing in.  We are kind of cheating, because there is a $creds variable that gets passed to the script, but we don't want to use those.  We're NOT using a credential alias because we don't want Powershell to try to log into that host - we want to have our script connect to that host, so we have to instantiate a new Powershell Credential object in our script, then pass that to the Invoke-RestMethod function

$pwd = ConvertTo-SecureString $pass_word -AsPlainText -Force
$credentials = New-Object System.Management.Automation.PSCredential($user_name,$pwd)

$endpoint = $uri+$resource+$params
$headers = New-Object "System.Collections.Generic.Dictionary[[String],[String]]"
$headers.Add('Accept', 'application/json')
$method = 'get'
$response = Invoke-RestMethod -Headers $headers -Method $method -Uri $endpoint -Credential 
$response | ConvertTo-Json -Compress

And that's it.  You can change that Invoke-RestMethod to a try - catch block and return additional information, but if it errors out, those errors get returned already to the Action.  But if you want to handle those more gracefully, you can change modify that script and have it send the output as Raw text, or into separated objects (headers, body, etc.) 

But this met our need, and others might find it useful as well.

 
4 REPLIES 4

Rong Chen
Kilo Contributor

Thank you Jacob, but I don't see anything about NTLM in your code. Do you need to specify some NTLM option?

I used to use curl command to get the security token like this:

> curl -s -i --ntlm --user 'name:pass' 'some url'

NBeheydt
Tera Expert

Is the Host in the Connection details the midserver IP address?  I'm trying to implement this and am getting "coud not create SSL/TSL secure channel' message.

Thanks

Add this to the beginning of your PS script to solve the SSL/TLS issue:

[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12

 

Michael Hays
Tera Expert

Change your Powershell script to this

 

param (
$url = ${uri},
$username = ${user_name},
$password = ${pass_word},
$domain = 'example.com'
)

$secpasswd = ConvertTo-SecureString $password -AsPlainText -Force
$credentials = New-Object System.Management.Automation.PSCredential("$domain\$username", $secpasswd)

try {
$response = Invoke-RestMethod -Uri $url -Method Get -Credential $credentials
$responseContent = $response | ConvertTo-Json
Write-Output $responseContent
} catch {
Write-Output $_.Exception.Message
}