curl to Invoke-RestMethod conversion

I’m trying to convert this curl command to Invoke-RestMethod.

The curl command is…

curl --insecure -i  -H "Content-type: application/x-www-form-urlencoded" -H "Accept: appliation/json" --data  "grant_type=access_code&assertion=eyJhbGciOiJub25lIn0K.eyJhdWQiOiAiaHR0cHM6Ly90XXXXXXCJpYXQiOiAiMTQwMjY4MjQ5NSJ9.&state=state_string" -X POST https://192.168.110.90/api/common/1.0/oauth/token

So far I have the following but I’m not sure where to put ‘Accept: appliation/json’? Any assistance would be greatly appreciated.

$Params = @{
    ContentType = 'application/x-www-form-urlencoded' 
    Body = 'grant_type=access_code&assertion=eyJhbGciOiJub25lIn0K.eyJhdWQiOiAiaHR0cHM6Ly90XXXXXXCJpYXQiOiAiMTQwMjY4MjQ5NSJ9.&state=state_string'
    Method = 'Post'
    URI = 'https://192.168.110.90/api/common/1.0/oauth/token'
}

Invoke-RestMethod @Params

Please check if one of the examples below work for you.

Example 1:

$Params = @{
    ContentType = 'application/x-www-form-urlencoded' 
    Body = 'grant_type=access_code&assertion=eyJhbGciOiJub25lIn0K.eyJhdWQiOiAiaHR0cHM6Ly90XXXXXXCJpYXQiOiAiMTQwMjY4MjQ5NSJ9.&state=state_string'
    Method = 'Post'
    URI = 'https://192.168.110.90/api/common/1.0/oauth/token'
    Headers = @{'accept'='application/json'}
}

Invoke-RestMethod @Params

Example 2:

$Params = @{
    Body = 'grant_type=access_code&assertion=eyJhbGciOiJub25lIn0K.eyJhdWQiOiAiaHR0cHM6Ly90XXXXXXCJpYXQiOiAiMTQwMjY4MjQ5NSJ9.&state=state_string'
    Method = 'Post'
    URI = 'https://192.168.110.90/api/common/1.0/oauth/token'
    Headers = @{'accept'='application/json';'content-type'='application/x-www-form-urlencoded'}
}

Invoke-RestMethod @Params

Hi Daniel, thanks for the quick reply.

Both examples gives me “Invoke-RestMethod : The remote server returned an error: (400) Bad Request.”

I suspect the issue is with Body. RESTful is a pain in the ass.

Please check if either of the following works instead.

$Params = @{
    ContentType = 'application/x-www-form-urlencoded' 
    Method = 'Post'
    URI = 'https://192.168.110.90/api/common/1.0/oauth/token?grant_type=access_code&assertion=eyJhbGciOiJub25lIn0K.eyJhdWQiOiAiaHR0cHM6Ly90XXXXXXCJpYXQiOiAiMTQwMjY4MjQ5NSJ9.&state=state_string'
    Headers = @{'accept'='application/json'}
}
Invoke-RestMethod @Params
$Params = @{
    Method = 'Post'
    URI = 'https://192.168.110.90/api/common/1.0/oauth/token?grant_type=access_code&assertion=eyJhbGciOiJub25lIn0K.eyJhdWQiOiAiaHR0cHM6Ly90XXXXXXCJpYXQiOiAiMTQwMjY4MjQ5NSJ9.&state=state_string'
    Headers = @{'accept'='application/json';'content-type'='application/x-www-form-urlencoded'}
}
Invoke-RestMethod @Params

Still getting the (400) Bad Request error Daniel. Thank you so much for your suggestions. I’m going to open a case with the vendor.

Try this. Specifying the headers this way has worked well for me using other RESTful APIs.

$headers = New-Object "System.Collections.Generic.Dictionary[[String],[String]]"
$headers.Add('Content-type', 'application/x-www-form-urlencoded'")
$headers.Add('Accept', 'application/json')

$args = @{
    Uri = "https://192.168.110.90/api/common/1.0/oauth/token"
    Method = "Post"
    Headers = $headers
    Body = 'grant_type=access_code&assertion=eyJhbGciOiJub25lIn0K.eyJhdWQiOiAiaHR0cHM6Ly90XXXXXXCJpYXQiOiAiMTQwMjY4MjQ5NSJ9.&state=state_string'
}

Invoke-RestMethod @args

Thanks for the suggestion Curtis. Still no luck. Same 400 error.

The REST API document (PDF) is here

It says…

All access to protected resources requires a valid access token. To obtain an access token, the client must send a POST request with the access code. (See the oauth section under Resources for the API.) The Steelhead appliance will issue an access token that is valid for the next one hour time period and return that token in the body of the POST. If the client script runs for over an hour, the appliance must generate another access token when the old one expires. An expired token results in an error with HTTP code 401 and error_id AUTH_EXPIRED_TOKEN. Finally, the Authorization HTTP Header must be used to pass the token for API authentication. The following format should be used: Authorization: Bearer 'Access Token encoded in base64 format' Example: Authorization: Bearer eyJhdWQiOiAiaHR0cHM6Ly9nZW4tdnNoNDMubGFiLm5idHRlY2guY29tAifQ==

Below the full doco with the example I was providing before. Maybe you guys will see something I’m missing.

Issue

How do you make REST API calls on the Steelhead ?

Solution

Starting with RioS 8.5 Steelheads support REST API Access. To enable this feature
•In the Steelhead web GUI ,
Configure > Security > REST API Access –> Enable REST API Access
•Preconfigure the access code
Configure > Security > REST API Access to display the REST API Access page. –> Click Add Access Code –> Generate New Access Code/Import Existing Access Code –> Add
•Copy the access code copied from the Management Console REST API Access page into the configuration file of your external script. The script uses the access code to make a call to the Steelhead appliance to request an access token.

The appliance/system validates the access code and returns an access token for use by the script. Generally the access token is kept by the script for a session only, but note that the script can make many requests using the same access token. These access tokens have some lifetime—usually around an hour —in which they are valid. When they expire, the access code must fetch a new access token. The script uses the access token to make REST API calls with the appliance/system.

Using CURL to obtain a token

[user@tbsl6 ~]$ curl --insecure -i -H “Content-type: application/x-www-form-urlencoded” -H “Accept: appliation/json” --data “grant_type=access_code&assertion=eyJhbGciOiJub25lIn0K.eyJhdWQiOiAiaHR0cHM6Ly90XXXXXXCJpYXQiOiAiMTQwMjY4MjQ5NSJ9.&state=state_string” -X POST https://192.168.110.90/api/common/1.0/oauth/token

HTTP/1.1 200 OK
Date: Fri, 27 Jun 2014 21:33:37 GMT

Server: PasteWSGIServer/0.5 Python/2.7.3
Content-type: application/json
Vary: Accept-Encoding,User-Agent
Transfer-Encoding: chunked
{

“access_token”: “eyJhdWQiOiAiaHR0cHM6Ly90…QwMzkwODQxNyIsICJpYXQiOiAiMTQwMzkwNDgxNyJ9”,
“allowed_signature_types”: [
“none”
],
“expires_in”: 3600,
“state”: “state_string”,
“token_type”: “bearer”
}

Note:

The assertion string, which is of a format ‘a.b.c’, where a = base64(‘{“alg”:“none”}’), b = base64(access code) and c = empty string. base64() is the function that encodes the string passed to base64 format. Use the corresponding function in your language of implementation. Also, in ‘a’, the algorithm is ‘none’, because the only signature method currently supported is ‘none’.

In the example above ,

assertion=eyJhbGciOiJub25lIn0K.eyJhdWQiOiAiaHR0cHM6Ly90XXXXXXCJpYXQiOiAiMTQwMjY4MjQ5NSJ9.

a= ‘eyJhbGciOiJub25lIn0K’ ==> b64encode(‘{“alg”:“none”}\n’)
b= access code from Steelhead
c= empty string

Making a REST API authenticated call using CURL

[user@tbsl6 ~]$ curl --insecure -i -H “Content-type: application/x-www-form-urlencoded” -H “Accept: application/json” -H “Authorization: Bearer eyJhdWQiOiAiaHR0cHM6Ly90…QwMzkwODQxNyIsICJpYXQiOiAiMTQwMzkwNDgxNyJ9” https://192.168.110.90/api/rfwk/1.0/system/uptime

HTTP/1.1 200 OK
Date: Fri, 27 Jun 2014 21:45:07 GMT
Server: PasteWSGIServer/0.5 Python/2.7.3
Content-type: application/json
Vary: Accept-Encoding,User-Agent
Transfer-Encoding: chunked
{
“uptime”: “3735163904”
}

and here is a Python example…I’m wondering if it’s because I’m not doing base64 encoding.

import os,sys
import base64
import json,time
import httplib, urllib

def base64_encode(s):
   return base64.urlsafe_b64encode(s)

def GET(sh,token,url):
   auth_token="Bearer " + ''.join(token)
   headers = {"Content-type": "application/x-www-form-urlencoded","Accept": "application/json","Authorization": auth_token}
   conn = httplib.HTTPSConnection(sh)
   conn.request("GET", url, auth_token, headers)
   response_data = conn.getresponse().read()
   print "GET:",url," response:\n", response_data

# Main
steelhead_ip="X.X.X.X"
access_code="eyJhdWQiOiAiaHR0cHM6Ly90Yi1zZXJ2ZXJzaC9hcGkvY29tbW9uLzEuMC90b2tlbiIsICJpc3MiOiAiaHR0cHM6Ly90Yi1zZXJ2ZXJzaCIsICJwcm4iOiAiYWRtaW4iLCAianRpIjogIjI2NjA5YzM0LWE1ZDAtNGZmZS05YWViLThjMjZhMzMxMTY1MiIsICJleHAiOiAiMCIsICJpYXQiOiAiMTQwMjY4MjQ5NSJ9"
header_encoded = base64_encode("{\"alg\":\"none\"}\n")
assertion_string=''.join([header_encoded,'.',access_code,'.',''])

# Create OAuth request
header = {"Content-type": "application/x-www-form-urlencoded","Accept": "application/json"}
body = 'grant_type=%s&assertion=%s&state=%s' % ('access_code', assertion_string, 'state_string')

conn = httplib.HTTPSConnection(steelhead_ip)
conn.request("POST", "/api/common/1.0/oauth/token", body, header)
response = conn.getresponse()
rdata = response.read()
conn.close()

jdata=json.loads(rdata)
if response.status == 200:
    access_token=jdata['access_token']
    print "\naccess_token=%s\nExpires in=%s\n" % (jdata['access_token'], jdata['expires_in'])
else:
    print response.status, response.reason

print "Rest API calls using token"

GET(steelhead_ip,access_token,"/api/rfwk/1.0/system/uptime")
GET(steelhead_ip,access_token,"/api/sh/1.0/status/health")
GET(steelhead_ip,access_token,"/api/common/1.0/info")

Thanks for the link to the PDF and the sample Python code.

According to the document the API expects a JSON request body too which does not make sense if you look at the Curl and Python examples. However, I’ve created a couple of examples below of which one will hopefully work for you.

As I understand (page 3 of the PDF), you’ll need to go into appliance portal and generate an access code, and replace the place holders in my examples before going ahead. Obviously the access code provided in the Curl and Python examples won’t work for the appliance you’ve access to.

I hope that helps. I can offer you to have a Zoom screen sharing session for free if you’re still stuck with getting this to work. Unfortunately, I don’t have access to this kind of appliance. Please let us know.

Example 1a - JSON request body and access code with Base64 encoding:

$uri = 'https://192.168.110.90/api/common/1.0/oauth/token'
$algorithm = "{`"alg`":`"none`"}\n"
$accessCode = 'PasteAccessCodeFromAppliancePortalHere'

$algorithmBase64 = [Convert]::ToBase64String([System.Text.Encoding]::Default.GetBytes($algorithm))
$accessCodeBase64 = [Convert]::ToBase64String([System.Text.Encoding]::Default.GetBytes($accessCode))

$params = @{
    ContentType = 'application/x-www-form-urlencoded'
    Headers = @{'accept'='application/json'}
    Body = @{
        'grant_type' = 'access_code'
        'assertion' = '{0}.{1}.' -f $algorithmBase64, $accessCodeBase64
        'state' = 'state_string'
    } | ConvertTo-Json
    Method = 'Post'
    URI = $uri
}
Invoke-RestMethod @params

Example 1b - JSON request body and access code w/o Base64 encoding:

$uri = 'https://192.168.110.90/api/common/1.0/oauth/token'
$algorithm = "{`"alg`":`"none`"}\n"
$accessCode = 'PasteAccessCodeFromAppliancePortalHere'

$algorithmBase64 = [Convert]::ToBase64String([System.Text.Encoding]::Default.GetBytes($algorithm))

$params = @{
    ContentType = 'application/x-www-form-urlencoded'
    Headers = @{'accept'='application/json'}
    Body = @{
        'grant_type' = 'access_code'
        'assertion' = '{0}.{1}.' -f $algorithmBase64, $accessCode
        'state' = 'state_string'
    } | ConvertTo-Json
    Method = 'Post'
    URI = $uri
}
Invoke-RestMethod @params

Example 2a - URI request body format and access code with Base64 encoding:

$uri = 'https://192.168.110.90/api/common/1.0/oauth/token'
$algorithm = "{`"alg`":`"none`"}\n"
$accessCode = 'PasteAccessCodeFromAppliancePortalHere'

$algorithmBase64 = [Convert]::ToBase64String([System.Text.Encoding]::Default.GetBytes($algorithm))
$accessCodeBase64 = [Convert]::ToBase64String([System.Text.Encoding]::Default.GetBytes($accessCode))

$params = @{
    ContentType = 'application/x-www-form-urlencoded'
    Headers = @{'accept'='application/json'}
    Body = 'grant_type=access_code&assertion={0}.{1}.&state=state_string' -f $algorithmBase64, 
        $accessCodeBase64
    Method = 'Post'
    URI = $uri
}
Invoke-RestMethod @params

Example 2b - URI request body format and access code w/o Base64 encoding:

$uri = 'https://192.168.110.90/api/common/1.0/oauth/token'
$algorithm = "{`"alg`":`"none`"}\n"
$accessCode = 'PasteAccessCodeFromAppliancePortalHere'

$algorithmBase64 = [Convert]::ToBase64String([System.Text.Encoding]::Default.GetBytes($algorithm))

$params = @{
    ContentType = 'application/x-www-form-urlencoded'
    Headers = @{'accept'='application/json'}
    Body = 'grant_type=access_code&assertion={0}.{1}.&state=state_string' -f $algorithmBase64, 
        $accessCode
    Method = 'Post'
    URI = $uri
}

Invoke-RestMethod @params

@Daniel, thank you so much for all the new examples. I’ve been using the access code generated by the appliance the whole time. Thanks for checking. Unfortunately all of them returned the same 400 error. I think we’re heading in the right direction though.

As for the Zoom screen share offer…I am interested. What timezone are you in? I’m in GMT 10:00+.

want to mention: access code in your python example is base64 encoded json string.
may be you need to properly fill needed properties before base64 encode it ?

PS D:\> $access_code="eyJhdWQiOiAiaHR0cHM6Ly90Yi1zZXJ2ZXJzaC9hcGkvY29tbW9uLzEuMC90b2tlbiIsICJpc3MiOiAiaHR0cHM6Ly90Yi1zZXJ2ZXJzaCIs
ICJwcm4iOiAiYWRtaW4iLCAianRpIjogIjI2NjA5YzM0LWE1ZDAtNGZmZS05YWViLThjMjZhMzMxMTY1MiIsICJleHAiOiAiMCIsICJpYXQiOiAiMTQwMjY4MjQ5NSJ9"
>>>
PS D:\> [text.encoding]::utf8.getstring([Convert]::FromBase64String($access_code))
{"aud": "https://tb-serversh/api/common/1.0/token", "iss": "https://tb-serversh", "prn": "admin", "jti": "26609c34-a5d0-4ffe-9aeb-8c26a3311652", "exp": "0", "iat": "1402682495"}

btw, may be your problem is encoding ? as far as I see, server want utf8, may be you sent your strings in unicode?

… not unicode… its Default in Daniel’s code

fyi, the post from Gareth Shipp is me. For some strange reason Powershell.org is now automatically logging me into the forums with what Outlook account I’m logged in with in another browser. I can no longer choose what email address to use when logging into the forums. Weird!

Gareth, no problem.

I am GMT+7 for another week. Are you on LinkedIn, Twitter, Facebook or an open Slack team like DevOps Chat, hangsops or PowerShell? To connect up and schedule a Zoom session without sharing our email addresses here.

My contact details:
https://www.linkedin.com/in/krebsdaniel
https://twitter.com/Dan1el42

Slack user name: dan1el42

Cheers,
Daniel

I’m on the Powershell Slack. Should be online within 45 min. If you’re on too then, great!

@Daniel, thank you so much for the session today. You’ve solved the mystery!

Fyi to everyone else…

Powershell was not using the correct base64 encoding. By copying the base64 code from a Python we managed to make a REST call to the appliance.

I’ve tried all the other types with [Convert]::ToBase64String([System.Text.Encoding]:: but still cannot match what Python produces. I’ll have to investigate further.

Final code below:

$uri = 'https://mydevice/api/common/1.0/oauth/token'
$algorithm = "{`"alg`":`"none`"}"
$accessCode = 'myacesscode'

$algorithmBase64 = [Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes($algorithm))

$params = @{
    ContentType = 'application/x-www-form-urlencoded'
    Headers = @{'accept'='application/json'}
    #Body = 'grant_type=access_code&assertion={0}.{1}.&state=state_string' -f $algorithmBase64,$accessCode
    Body = 'grant_type=access_code&assertion={0}.{1}.&state=state_string' -f 'eyJhbGciOiJub25lIn0K',$accessCode
    Method = 'Post'
    URI = $uri
}

Thanks Gareth.

I’ve figured out why PowerShell returned a different Base64 string for the algorithm soon after our call had ended. I’ve made a mistake and missed to convert all escape characters from Python to PowerShell.

Wrong:
$algorithm = “{`"alg`”:`"none`"}\n"

Correct:
$algorithm = “{`"alg`”:`"none`"}`n"

$uri = 'https://mydevice/api/common/1.0/oauth/token'
$algorithm = "{`"alg`":`"none`"}`n"
$accessCode = 'myacesscode'

$algorithmBase64 = [Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes($algorithm))

$params = @{
    ContentType = 'application/x-www-form-urlencoded'
    Headers = @{'accept'='application/json'}
    Body = 'grant_type=access_code&assertion={0}.{1}.&state=state_string' -f $algorithmBase64, $accessCode
    Method = 'Post'
    URI = $uri
}