system.net.httpClient().post throwing 'unsupported_grant_type' error

I am using system.net.httpClient().post to issue a refresh token but for some reason I am getting an ‘unsupported_grant_type’ error. Here’s my code:

from base64 import b64encode
logger = system.util.getLogger("myLogger")


client_id = system.tag.read("[WFM_API]client_id").value
client_secret =  system.tag.read("[WFM_API]client_secret").value
AccessToken = system.tag.read("[WFM_API]Tokens/AccessToken").value
scope = system.tag.read("[WFM_API]scopes").value
redirect_url = 'https://xero.com/'
RefreshToken = system.tag.read("[WFM_API]Tokens/RefreshToken").value

b64_id_secret = b64encode(client_id + ':' + client_secret)

def XeroRefreshToken(refresh_token):
   token_refresh_url = 'https://identity.xero.com/connect/token'
   client = system.net.httpClient()
   response = client.post(token_refresh_url,
                           headers = {
                               'Authorization' : 'Basic ' + b64_id_secret,
                               'Content-Type': 'application/x-www-form-urlencoded'
                           },
                           data = {
                               'grant_type' : 'refresh_token',
                               'refresh_token' : refresh_token
                           })
   json_response = response.json
   print json_response
   logger.info(str(json_response))
   
XeroRefreshToken(RefreshToken)

I’ve also tried using the system.net.httpPost function and that returned the same error too.
I used the urlencode function on the “body” parameter by referring to this post here, but that also threw the same error. Any help is much appreciated.

Ignition Version 8.1.16

Do you have any documentation on what this API actually expects?

Yes, and I think I am doing it right. This is what they’re expecting Refreshing access & refresh tokens. I also forgot to mention that I am having no issues refreshing the token using postman.
Here are the headers:

Body:

Settings:

I don’t think either of the system.net methods can automatically encode your payload as “form-data” or “application/x-www-form-urlencoded” data.

You’re passing in a dictionary and it’s almost certainly just being encoded as a JSON object and sent off as the body.

You may need to manually concatenate the payload into a String with the correct format instead.

I tried urlencoding my ‘data’ argument and also tried using it as a string and both times I got a
{‘error’: u’invalid_grant’}

URL Encoding:

def XeroRefreshToken(refresh_token):
from urllib import urlencode
token_refresh_url = 'https://identity.xero.com/connect/token'
	client = system.net.httpClient()
	response = client.post(token_refresh_url,
                            headers = {
                                'Authorization' : 'Basic ' + b64_id_secret,
                                'Content-Type': 'application/x-www-form-urlencoded'
                            },
                            data = urlencode({
                                'grant_type' : 'refresh_token',
                                'refresh_token' : refresh_token
                            }))
	json_response = response.json
	print json_response

Concatenating in a string:

def XeroRefreshToken(refresh_token):
	token_refresh_url = 'https://identity.xero.com/connect/token'
	client = system.net.httpClient()
	response = client.post(token_refresh_url,
                            headers = {
                                'Authorization' : 'Basic ' + b64_id_secret,
                                'Content-Type': 'application/x-www-form-urlencoded'
                            },
                            data = 'grant_type=refresh_token&refresh_token=%s'%refresh_token
                            )
	json_response = response.json
	print json_response
	
XeroRefreshToken(RefreshToken)```

Try URL-encoding this String you build:

'grant_type=refresh_token&refresh_token=%s'%refresh_token

Your two examples are either URL-encoding the string representation of a dictionary or not URL encoding the data at all.

My bad, thought I only had to encode the dictionary. Looks like urlencode only works with dictionaries because it was returning TypeError: not a valid non-string sequence or mapping object when I was using it with a string. Did some research and it looks like I have to use urllib.quote or urllib.quote_plus with a string and non of them worked. Both returned {'error': u'unsupported_grant_type'}

Hmm, well I just looked at the docs for urlencode, and you can pass it a dictionary, which I didn’t know.

I’m assuming you can’t access the URL via plain HTTP, so you might need to get some kind of local HTTP mitm/dev proxy set up that will let you see what the difference between what you’re sending via Postman and Ignition is.

Just figured it out! I clicked on the code snippet icon </> in PostMan and selected the ‘Python - http.client’ option from the dropdown. I saw that the code was importing the encode module from a library called codecs. So I tried it out and system.net.httpClient().post is returning the expected response now. My script became like this:


client_id = system.tag.read("[WFM_API]client_id").value
client_secret =  system.tag.read("[WFM_API]client_secret").value
AccessToken = system.tag.read("[WFM_API]Tokens/AccessToken").value
scope = system.tag.read("[WFM_API]scopes").value
redirect_url = 'https://xero.com/'
RefreshToken = system.tag.read("[WFM_API]Tokens/RefreshToken").value

b64_id_secret = b64encode(client_id + ':' + client_secret)

def XeroRefreshToken(refresh_token):
	from codecs import encode
	token_refresh_url = 'https://identity.xero.com/connect/token '
	client = system.net.httpClient()
	response = client.post(token_refresh_url,
                            headers = {
                                'Authorization' : 'Basic ' + b64_id_secret,
                                'Content-Type': 'application/x-www-form-urlencoded'
                            },
                            data = encode('grant_type=refresh_token&refresh_token=%s'%refresh_token)
                            )
	json_response = response.json
	print json_response	

XeroRefreshToken(RefreshToken)

Thanks Kevin for your help.

3 Likes