We're trying to call a REST API hosted in our NetSuite account. NetSuite requires a proprietary Authorization type. We tried providing the Authorization string in the Authentication field but Flow complained about the Type:
{ "Authorization":"NLAuth nlauth_account=xxxxxx, nlauth_email=xxxxxxx@xxxxx.com, nlauth_signature=xxxxxxxxxxxxxxx, nlauth_role=xxxx"}
We then tried defining the Authorization header in the main headers field and this worked, however we also need to define the content-type and this appears to be ignored.
{ "Authorization":"NLAuth nlauth_account=xxxxxxxxxx, nlauth_email=xxxxxxx@xxxxxxxx.com, nlauth_signature=xxxxxxxxx, nlauth_role=xxxx","Content-Type":"application/json" }
The NetSuite REST api returns a JSON payload, if we don't explicitly set the Content-Type to "application/json" we get errors. If I temporarily trick the API to return only text, everything works fine which proves the Authorization header is working.
What are we doing wrong?
Hey all- Very old thread so apologies for resurrecting it, but this has been a useful starting point for my own PowerShell -> NetSuite API HRIS automations. That said, I am only getting the Invalid Login Attempt (401) error. I am using the above "Wall of Code" that was reworked for REST API, which my user/role/token is configured to use. Everything works in my Postman environment, but... not in my local PowerShell script.
I would be forever grateful if we could dig back into this and see what might be wrong with my code or if something with the REST API has changed in the few years since the above code was working.
Thanks to @rgmatthes1 - I wouldn't have persisted without your hints.
Updated for REST API (still in beta, so YMMV).
Wall-of-code:
add-type @"
using System.Net;
using System.Security.Cryptography.X509Certificates;
public class TrustAllCertsPolicy : ICertificatePolicy {
public bool CheckValidationResult(
ServicePoint srvPoint, X509Certificate certificate,
WebRequest request, int certificateProblem) {
return true;
}
}
"@
$AllProtocols = [System.Net.SecurityProtocolType]'Ssl3,Tls,Tls11,Tls12'
[System.Net.ServicePointManager]::SecurityProtocol = $AllProtocols
[System.Net.ServicePointManager]::CertificatePolicy = New-Object TrustAllCertsPolicy
#--------REST v1.1 - Sharepoint Access, REST_Access_No2FA
$oauth_consumer_key = "Integration Consumer Key here".ToUpper()
$oauth_consumer_secret = "Integration Consumer Secret here".ToLower()
$oauth_token = "Access Token ID here".ToUpper()
$oauth_token_secret = "Access Token Secret here".ToLower()
$oauth_signature_method = "HMAC-SHA256"
$oauth_version = "1.0"
$realm = "Your Realm" #This is *different* from the URL e.g. 1234567-sb1 becomes 1234567_SB1
$HTTP_method = "GET"
$url = "https://$($realm.ToLower().Replace("_","-")).suitetalk.api.netsuite.com"
#$query = "/rest/platform/v1/metadata-catalog/record?select=customer"
$query = "/rest/platform/v1/metadata-catalog/record/customer"
if($query -match "\?"){
$parameters = $query.Split("?")[1]
$query = $query.Split("?")[0]
}
else{$parameters = ""}
$oauth_nonce = [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes([System.DateTime]::Now.Ticks.ToString()))
$oauth_timestamp = [int64](([datetime]::UtcNow)-(Get-Date "1970-01-01")).TotalSeconds
$oAuthParamsForSigning = @{}
$oAuthParamsForSigning.Add("oauth_consumer_key",$oauth_consumer_key)
$oAuthParamsForSigning.Add("oauth_token",$oauth_token)
$oAuthParamsForSigning.Add("oauth_signature_method",$oauth_signature_method)
$oAuthParamsForSigning.Add("oauth_version",$oauth_version)
$oAuthParamsForSigning.Add("oauth_nonce",$oauth_nonce)
$oAuthParamsForSigning.Add("oauth_timestamp",$oauth_timestamp)
$parameters.Split("&") | % {
if(![string]::IsNullOrWhiteSpace($_.Split("=")[0])){
$oAuthParamsForSigning.Add($_.Split("=")[0],$_.Split("=")[1])
}
}
$oAauthParamsString = ($oAuthParamsForSigning.Keys | Sort-Object | % {
"$_=$($oAuthParamsForSigning[$_])"
}) -join "&"
$encodedOAuthParamsString = [uri]::EscapeDataString($oAauthParamsString)
$encodedUrl = [uri]::EscapeDataString($url+$query)
$base_string = $HTTP_method + "&" + $encodedUrl + "&" + $encodedOAuthParamsString
$key = $oauth_consumer_secret + "&" + $oauth_token_secret
$hmacsha265 = new-object System.Security.Cryptography.HMACSHA256
$hmacsha265.Key = [System.Text.Encoding]::ASCII.GetBytes($key)
$oauth_signature = [System.Convert]::ToBase64String($hmacsha265.ComputeHash([System.Text.Encoding]::ASCII.GetBytes($base_string)))
#$oauth_signature - can be compared with PostMan and http://lti.tools/oauth/
$authHeaderString = ($oAuthParamsForSigning.Keys | Sort-Object | % {
"$_=`"$([uri]::EscapeDataString($oAuthParamsForSigning[$_]))`""
}) -join ","
$authHeaderString += ",realm=`"$([uri]::EscapeDataString($realm))`""
$authHeaderString += ",oauth_signature=`"$([uri]::EscapeDataString($oauth_signature))`""
$response = Invoke-RestMethod -Uri $([uri]::EscapeUriString($url+$query)) -Headers @{"Authorization"="OAuth $authHeaderString";"Cache-Control"="no-cache";"Accept"="application/swagger+json";"Accept-Encoding"="gzip, deflate"} -Method $HTTP_method -Verbose -ContentType "application/swagger+json"
Broken into functions in a module:
add-type @"
using System.Net;
using System.Security.Cryptography.X509Certificates;
public class TrustAllCertsPolicy : ICertificatePolicy {
public bool CheckValidationResult(
ServicePoint srvPoint, X509Certificate certificate,
WebRequest request, int certificateProblem) {
return true;
}
}
"@
$AllProtocols = [System.Net.SecurityProtocolType]'Ssl3,Tls,Tls11,Tls12'
[System.Net.ServicePointManager]::SecurityProtocol = $AllProtocols
[System.Net.ServicePointManager]::CertificatePolicy = New-Object TrustAllCertsPolicy
function get-netsuiteAuthHeaders(){
[cmdletbinding()]
Param (
[parameter(Mandatory = $true)]
[ValidateSet("GET","POST")]
[string]$requestType
,[parameter(Mandatory = $true)]
[ValidatePattern("http")]
[string]$url
,[parameter(Mandatory=$true)]
[hashtable]$oauthParameters
,[parameter(Mandatory=$true)]
[string]$oauth_consumer_secret
,[parameter(Mandatory=$false)]
[string]$oauth_token_secret
,[parameter(Mandatory=$true)]
[string]$realm
)
$oauth_signature = get-oauthSignature -requestType $requestType -url $url -oauthParameters $oauthParameters -oauth_consumer_secret $oauth_consumer_secret -oauth_token_secret $oauth_token_secret
$authHeaderString = ($oauthParameters.Keys | Sort-Object | % {
"$_=`"$([uri]::EscapeDataString($oauthParameters[$_]))`""
}) -join ","
$authHeaderString += ",realm=`"$([uri]::EscapeDataString($realm))`""
$authHeaderString += ",oauth_signature=`"$([uri]::EscapeDataString($oauth_signature))`""
@{"Authorization"="OAuth $authHeaderString"
;"Cache-Control"="no-cache"
;"Accept"="application/swagger+json"
;"Accept-Encoding"="gzip, deflate"
}
}
function get-netsuiteParameters(){
[cmdletbinding()]
Param()
#Don't really store your keys and secrets in plaintext like this - it's just proof-of-concept
@{oauth_consumer_key = "Integration Consumer Key here".ToUpper()
;oauth_consumer_secret = "Integration Consumer Secret here".ToLower()
;oauth_token = "Access Token ID here".ToUpper()
;oauth_token_secret = "Access Token Secret here".ToLower()
;oauth_signature_method = "HMAC-SHA256"
;oauth_version = "1.0"
;realm = "Your Realm"
}
}
function get-oauthSignature(){
[cmdletbinding()]
Param (
[parameter(Mandatory = $true)]
[ValidateSet("GET","POST")]
[string]$requestType
,[parameter(Mandatory = $true)]
[ValidatePattern("http")]
[string]$url
,[parameter(Mandatory=$true)]
[hashtable]$oauthParameters
,[parameter(Mandatory=$true)]
[string]$oauth_consumer_secret
,[parameter(Mandatory=$false)]
[string]$oauth_token_secret
)
$requestType = $requestType.ToUpper()
$encodedUrl = [uri]::EscapeDataString($url.ToLower())
$oAauthParamsString = (
$oauthParameters.Keys | Sort-Object | % {
if(@("realm","oauth_signature") -notcontains $_){
"$_=$($oauthParameters[$_])"
}
}
) -join "&"
$encodedOAuthParamsString = [uri]::EscapeDataString($oAauthParamsString)
$base_string = $requestType + "&" + $encodedUrl + "&" + $encodedOAuthParamsString
$key = $oauth_consumer_secret + "&" + $oauth_token_secret
Switch($oauthParameters["oauth_signature_method"]){
"HMAC-SHA1" {
$cryptoFunction = new-object System.Security.Cryptography.HMACSHA1
}
"HMAC-SHA256" {
$cryptoFunction = new-object System.Security.Cryptography.HMACSHA256
}
"HMAC-SHA384" {
$cryptoFunction = new-object System.Security.Cryptography.HMACSHA384
}
"HMAC-SHA512" {
$cryptoFunction = new-object System.Security.Cryptography.HMACSHA512
}
default {
Write-Error "Unsupported oauth_signature_method [$_]"
break
}
}
$cryptoFunction.Key = [System.Text.Encoding]::ASCII.GetBytes($key)
$oauth_signature = [System.Convert]::ToBase64String($cryptoFunction.ComputeHash([System.Text.Encoding]::ASCII.GetBytes($base_string)))
$oauth_signature
}
function invoke-netsuiteRestMethod(){
[cmdletbinding()]
Param(
[parameter(Mandatory = $true)]
[ValidateSet("GET","POST")]
[string]$requestType
,[parameter(Mandatory = $true)]
[ValidatePattern("http")]
[string]$url
,[parameter(Mandatory=$false)]
[hashtable]$netsuiteParameters
)
if(!$netsuiteParameters){$netsuiteParameters = get-netsuiteParameters}
if($url -match "\?"){
$parameters = $url.Split("?")[1]
$url = $url.Split("?")[0]
}
else{$parameters = ""}
$oauth_nonce = [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes([System.DateTime]::Now.Ticks.ToString()))
$oauth_timestamp = [int64](([datetime]::UtcNow)-(Get-Date "1970-01-01")).TotalSeconds
$oAuthParamsForSigning = @{}
#Add standard oAuth 1.0 parameters
$oAuthParamsForSigning.Add("oauth_nonce",$oauth_nonce)
$oAuthParamsForSigning.Add("oauth_timestamp",$oauth_timestamp)
$oAuthParamsForSigning.Add("oauth_consumer_key",$netsuiteParameters.oauth_consumer_key)
$oAuthParamsForSigning.Add("oauth_token",$netsuiteParameters.oauth_token)
$oAuthParamsForSigning.Add("oauth_signature_method",$netsuiteParameters.oauth_signature_method)
$oAuthParamsForSigning.Add("oauth_version",$netsuiteParameters.oauth_version)
#Add parameters from url
$parameters.Split("&") | % {
if(![string]::IsNullOrWhiteSpace($_.Split("=")[0])){
$oAuthParamsForSigning.Add($_.Split("=")[0],$_.Split("=")[1])
}
}
$netsuiteRestHeaders = get-netsuiteAuthHeaders -requestType $requestType -url $url -oauthParameters $oAuthParamsForSigning -oauth_consumer_secret $netsuiteParameters["oauth_consumer_secret"] -oauth_token_secret $netsuiteParameters["oauth_token_secret"] -realm $netsuiteParameters["realm"]
$response = Invoke-RestMethod -Uri $([uri]::EscapeUriString($url)) -Headers $netsuiteRestHeaders -Method $requestType -Verbose -ContentType "application/swagger+json"
$response
}
Usage:
invoke-netsuiteRestMethod -requestType GET -url "https://YourInstance.suitetalk.api.netsuite.com/rest/platform/v1/metadata-catalog/record/customer"
Old thread, but we had the same problem as you. Unfortunately, the solution isn't pretty. We ended up doing two things:
The two steps worked together for us. First we feed the URL and HTTP method (GET, POST, etc.) to the OAuth header generator, then we use the header to form a full HTTP request and via the second function app. This should be scaleable as you tackle more and more autoation with Azure.
You're trying exactly the same integration I am planning to do. Keep this thread update, please!
Hi Sklett,
This article about Custom APIs can be a reference for you:
https://powerapps.microsoft.com/en-us/tutorials/register-custom-api/
There is a note in the document stating “Support for API key authentication is coming soon”.
The article also has links for AAD Authentication that may be helpful.
Best regards,
Mabel Mao
WarrenBelz
146,631
Most Valuable Professional
RandyHayes
76,287
Super User 2024 Season 1
Pstork1
65,991
Most Valuable Professional