cancel
Showing results for 
Search instead for 
Did you mean: 
Reply
Highlighted
sklett
Level: Power Up

Content-Type header seems to be ignored with http action

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?

1 ACCEPTED SOLUTION

Accepted Solutions
rgmatthes1
Level: Power Up

Re: Content-Type header seems to be ignored with http action

Old thread, but we had the same problem as you. Unfortunately, the solution isn't pretty. We ended up doing two things:

 

  • Using OAuth instead of NLAuth. Passing account information such as a username and password isn't just insecure, it's fragile. If this user leaves the company, or your service account is decommissioned, stuff starts to break, and it's usually hard to pinpoint where. So we wrote a function app to generate an OAuth header for making calls into NetSuite. You can call the function app from within Logic Apps and have it return the header you need to make your HTTP request to NetSuite. Here's a starting point in PowerShell, but there are other Python examples out there if you Google: https://netsuitehub.com/forums/topic/oauth-powershell-restlet-working-script-example

    This script didn't take us all the way there. I don't think I'm allowed to post my final code, but:
    • You need to sort alphabetically all parameters and encode all values when constructing the signature (including "deploy" and "script"), which this code doesn't do. Powershell "sort" is your friend.
    • This script's nonce generation stinks. You don't want to limit yourself to just a random number, but a random, case-sensitive string. That way you have 62 characters at your disposal instead of 10.
    • Our base URL frustratingly did not start with "rest," but our NS account ID #. None of the NetSuite documentation included this, but we noticed when deploying our script that its external URL was formed differently.
    • Here's a site you can use to check your script-generated signature vs. what NetSuite is looking for: http://lti.tools/oauth/
  • Write our own HTTP request. Again, function apps to the rescue. Instead of using using the built-in Logic Apps step, which as you noted doesn't work well with NetSuite, we made another function app that uses the "Invoke-WebRequest" PowerShell cmdlet to make the web request, then return the result back to Logic Apps. This was just 10 lines of code including comments, so it was far easier than the last bit to write. My only word of advice is to remember that you can submit multiple headers via an HTTP request, which means you need to expand the JSON and go through each one (think "PSObject.Properties") and add the names and values to a hashtable, which you can reference when you get to the "Invoke-WebRequest" step.

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.

View solution in original post

4 REPLIES 4
Community Support Team
Community Support Team

Re: Content-Type header seems to be ignored with http action

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

Community Support Team _ Mabel Mao
If this post helps, then please consider Accept it as the solution to help the other members find it more quickly.
Anonymous
Not applicable

Re: Content-Type header seems to be ignored with http action

You're trying exactly the same integration I am planning to do. Keep this thread update, please!

rgmatthes1
Level: Power Up

Re: Content-Type header seems to be ignored with http action

Old thread, but we had the same problem as you. Unfortunately, the solution isn't pretty. We ended up doing two things:

 

  • Using OAuth instead of NLAuth. Passing account information such as a username and password isn't just insecure, it's fragile. If this user leaves the company, or your service account is decommissioned, stuff starts to break, and it's usually hard to pinpoint where. So we wrote a function app to generate an OAuth header for making calls into NetSuite. You can call the function app from within Logic Apps and have it return the header you need to make your HTTP request to NetSuite. Here's a starting point in PowerShell, but there are other Python examples out there if you Google: https://netsuitehub.com/forums/topic/oauth-powershell-restlet-working-script-example

    This script didn't take us all the way there. I don't think I'm allowed to post my final code, but:
    • You need to sort alphabetically all parameters and encode all values when constructing the signature (including "deploy" and "script"), which this code doesn't do. Powershell "sort" is your friend.
    • This script's nonce generation stinks. You don't want to limit yourself to just a random number, but a random, case-sensitive string. That way you have 62 characters at your disposal instead of 10.
    • Our base URL frustratingly did not start with "rest," but our NS account ID #. None of the NetSuite documentation included this, but we noticed when deploying our script that its external URL was formed differently.
    • Here's a site you can use to check your script-generated signature vs. what NetSuite is looking for: http://lti.tools/oauth/
  • Write our own HTTP request. Again, function apps to the rescue. Instead of using using the built-in Logic Apps step, which as you noted doesn't work well with NetSuite, we made another function app that uses the "Invoke-WebRequest" PowerShell cmdlet to make the web request, then return the result back to Logic Apps. This was just 10 lines of code including comments, so it was far easier than the last bit to write. My only word of advice is to remember that you can submit multiple headers via an HTTP request, which means you need to expand the JSON and go through each one (think "PSObject.Properties") and add the names and values to a hashtable, which you can reference when you get to the "Invoke-WebRequest" step.

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.

View solution in original post

kevmaitland
Level: Power Up

Re: Content-Type header seems to be ignored with http action

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"

 

 

Helpful resources

Announcements
firstImage

Power Platform Online Conference

Join us for the first ever Power Platform Online Conference!

firstImage

Coming Soon: T-shirt Design Contest

Keep your eyes open for our upcoming T-shirt design contest!

firstImage

Incoming: New and improved badges!

Look out for new contribution recognition badges coming SOON!

firstImage

New & Improved Power Automate Community Cookbook

We've updated and improved the layout and uploading format of the Power Automate Cookbook!

thirdimage

Power Automate Community User Group Member Badge

Fill out a quick form to claim your user group badge now!

sixthImage

Power Platform World Tour

Find out where you can attend!

Users online (6,014)