Oauth2 with Salesforce

Hello,

I have a very specific use case where I’m trying to implement Oauth2 against a Salesforce (SFDC) instance. I’ve been stuck at this point for a while now where I can get past the login prompt but no token is returned. I’m using this piece of documentation as a guideline: https://help.salesforce.com/articleView?id=remoteaccess_oauth_web_server_flow.htm&type=5

This is a break down of what I’ve done:

  1. Call back url is declared in SFDC, as well as all of the scopes (I have access to all of them).
  2. Passing scopes is required in order to get a refresh token - it took a lot of research to find this out since it’s not well documented. However, SFDC requires the tokens to be separated by %20, and not the standard +. I followed this guidance from @patrick to achieve the same.
  3. I have an Oauth2 instance declared in the Globals panel:

    and my SalesForce admin says he successfully uses the same parameters from Postman (which I have no experience of).
  4. I have an authorize instance in my PHP server side action:

When I try to run the code, I am prompted to login to SalesForce if I wasn’t previously (this is expected) and to authorize the app to do a number of actions on my behalf, and then the worflow breaks with this error:

{"code":0,"file":"\/home\/vcap\/app\/htdocs\/dmxConnectLib\/lib\/oauth\/Oauth2.php","line":266,"message":"Http response has no access_token","trace":"#0 \/home\/vcap\/app\/htdocs\/dmxConnectLib\/lib\/oauth\/Oauth2.php(230): lib\\oauth\\Oauth2->grant('authorization_c...', Array)\n#1 \/home\/vcap\/app\/htdocs\/dmxConnectLib\/modules\/oauth.php(24): lib\\oauth\\Oauth2->authorize(Array, Object(stdClass))\n#2 \/home\/vcap\/app\/htdocs\/dmxConnectLib\/lib\/App.php(193): modules\\oauth->authorize(Object(stdClass), 'sfdc_credential...')\n#3 \/home\/vcap\/app\/htdocs\/dmxConnectLib\/lib\/App.php(157): lib\\App->execSteps(Object(stdClass))\n#4 \/home\/vcap\/app\/htdocs\/dmxConnectLib\/lib\/App.php(127): lib\\App->execSteps(Array)\n#5 \/home\/vcap\/app\/htdocs\/dmxConnectLib\/lib\/App.php(116): lib\\App->exec(Object(stdClass), false)\n#6 \/home\/vcap\/app\/htdocs\/dmxConnectLib\/lib\/App.php(94): lib\\App->exec(Object(stdClass))\n#7 \/home\/vcap\/app\/htdocs\/dmxConnect\/api\/Escalations\/escalation_closure.php(8): lib\\App->define(Object(stdClass))\n#8 {main}"}

What is of interest seems to be: :"Http response has no access_token".

My only idea was that maybe the ‘code’ parameter needed to be passed on or declared from either the Oauth2 or Authorize steps, which I have tried one after the, simultaneously, and one without the other - it has not changed anything, I get the exact same behavior. The way I did this was by declaring a $_GET input for ‘code’:
image

And then referencing it where it could have made sense to me:

  • in the Oauth2 instance:
    image
    -in the Authorize instance:
    image

At this point I have exhausted my thin knowlegde and my ideas, I would appreciate any suggestions.
Thanks!
Nahtaniel.

Not sure what goes wrong, the error indicates that it didn’t fond the access_token in the response body. Could be that the body was not correctly parsed or it actually returned something without an access_token in it.

I have updated the code that it should return the actual response object also in the error to help us debug. Unzip the file in dmxConnectLib/lib/oauth.

Oauth2.zip (3.0 KB)

Hi Patrick, thanks for the debug file.
I’ve uploaded it after modifying line 295 to make it $uri .= http_build_query($params, null, '&', PHP_QUERY_RFC3986);, otherwise the page would have stopped complaining about not passing scopes as it expects.

The output seems to be exactly the same:

{"code":0,"file":"\/home\/vcap\/app\/htdocs\/dmxConnectLib\/lib\/oauth\/Oauth2.php","line":266,"message":"Http response has no access_token. ","trace":"#0 \/home\/vcap\/app\/htdocs\/dmxConnectLib\/lib\/oauth\/Oauth2.php(230): lib\\oauth\\Oauth2->grant('authorization_c...', Array)\n#1 \/home\/vcap\/app\/htdocs\/dmxConnectLib\/modules\/oauth.php(24): lib\\oauth\\Oauth2->authorize(Array, Object(stdClass))\n#2 \/home\/vcap\/app\/htdocs\/dmxConnectLib\/lib\/App.php(193): modules\\oauth->authorize(Object(stdClass), 'sfdc_credential...')\n#3 \/home\/vcap\/app\/htdocs\/dmxConnectLib\/lib\/App.php(157): lib\\App->execSteps(Object(stdClass))\n#4 \/home\/vcap\/app\/htdocs\/dmxConnectLib\/lib\/App.php(127): lib\\App->execSteps(Array)\n#5 \/home\/vcap\/app\/htdocs\/dmxConnectLib\/lib\/App.php(116): lib\\App->exec(Object(stdClass), false)\n#6 \/home\/vcap\/app\/htdocs\/dmxConnectLib\/lib\/App.php(94): lib\\App->exec(Object(stdClass))\n#7 \/home\/vcap\/app\/htdocs\/dmxConnect\/api\/Escalations\/escalation_closure.php(8): lib\\App->define(Object(stdClass))\n#8 {main}"}

Should I be running it differently to get more debug info?

In looking at the Chrome output, I see the issue seems to be with the Authorize step, but I don’t know if this capture helps any further:

Thanks @patrick

The change I made is on line 266, it seems that print_r isn’t returning anytthing, you can try var_export instead. It is also possible that there was an error parsing the results, you could check the json_last_error_msg() response.

Hi @patrick,

There was a restart of some services in the server overnight as there may have been an issue with cash, upon attempting to call the Oauth API I now run into this error:
[{"message":"Session expired or invalid","errorCode":"INVALID_SESSION_ID"}]

The salesforce admin is suggesting that what is broken is getting a refresh token, the initial one, he believes, is passed and retrieved correctly, but it is not the case when a user returns - maybe this is a pointer?

There is nothing else but the above message error, no code line referenced that I could see to add some debugging, and the error message itself seems returned from SalesForce itself.

I’ve added a client_secret parametter to the authorize step:
image

which seems to get me back to where we were yesterday.
Print_r keeps not returning anything, var_export returns NULL:

{"code":0,"file":"\/home1\/nathapb8\/public_html\/escalations\/tanzu\/dmxConnectLib\/lib\/oauth\/Oauth2.php","line":267,"message":"Http response has no access_token - var_export. NULL","trace":"#0 \/home1\/nathapb8\/public_html\/escalations\/tanzu\/dmxConnectLib\/lib\/oauth\/Oauth2.php(230): lib\\oauth\\Oauth2->grant('authorization_c...', Array)\n#1 \/home1\/nathapb8\/public_html\/escalations\/tanzu\/dmxConnectLib\/modules\/oauth.php(24): lib\\oauth\\Oauth2->authorize(Array, Object(stdClass))\n#2 \/home1\/nathapb8\/public_html\/escalations\/tanzu\/dmxConnectLib\/lib\/App.php(193): modules\\oauth->authorize(Object(stdClass), 'sfdc_credential...')\n#3 \/home1\/nathapb8\/public_html\/escalations\/tanzu\/dmxConnectLib\/lib\/App.php(157): lib\\App->execSteps(Object(stdClass))\n#4 \/home1\/nathapb8\/public_html\/escalations\/tanzu\/dmxConnectLib\/lib\/App.php(127): lib\\App->execSteps(Array)\n#5 \/home1\/nathapb8\/public_html\/escalations\/tanzu\/dmxConnectLib\/lib\/App.php(116): lib\\App->exec(Object(stdClass), false)\n#6 \/home1\/nathapb8\/public_html\/escalations\/tanzu\/dmxConnectLib\/lib\/App.php(94): lib\\App->exec(Object(stdClass))\n#7 \/home1\/nathapb8\/public_html\/escalations\/tanzu\/dmxConnect\/api\/Escalations\/escalation_closure.php(8): lib\\App->define(Object(stdClass))\n#8 {main}"}

And the json last error throws a generic syntax error :
{"code":0,"file":"\/home1\/nathapb8\/public_html\/escalations\/tanzu\/dmxConnectLib\/lib\/oauth\/Oauth2.php","line":269,"message":"Http response has no access_token - json_last_error_msg: Syntax error","trace":"#0 \/home1\/nathapb8\/public_html\/escalations\/tanzu\/dmxConnectLib\/lib\/oauth\/Oauth2.php(230): lib\\oauth\\Oauth2->grant('authorization_c...', Array)\n#1 \/home1\/nathapb8\/public_html\/escalations\/tanzu\/dmxConnectLib\/modules\/oauth.php(24): lib\\oauth\\Oauth2->authorize(Array, Object(stdClass))\n#2 \/home1\/nathapb8\/public_html\/escalations\/tanzu\/dmxConnectLib\/lib\/App.php(193): modules\\oauth->authorize(Object(stdClass), 'sfdc_credential...')\n#3 \/home1\/nathapb8\/public_html\/escalations\/tanzu\/dmxConnectLib\/lib\/App.php(157): lib\\App->execSteps(Object(stdClass))\n#4 \/home1\/nathapb8\/public_html\/escalations\/tanzu\/dmxConnectLib\/lib\/App.php(127): lib\\App->execSteps(Array)\n#5 \/home1\/nathapb8\/public_html\/escalations\/tanzu\/dmxConnectLib\/lib\/App.php(116): lib\\App->exec(Object(stdClass), false)\n#6 \/home1\/nathapb8\/public_html\/escalations\/tanzu\/dmxConnectLib\/lib\/App.php(94): lib\\App->exec(Object(stdClass))\n#7 \/home1\/nathapb8\/public_html\/escalations\/tanzu\/dmxConnect\/api\/Escalations\/escalation_closure.php(8): lib\\App->define(Object(stdClass))\n#8 {main}"}

Would this point to a JSON parsing issue?

Yes, it seems that the response did not have valid JSON, will make some small adjustments to the code so that we can debug the exact result from the server. Perhaps the result is not in JSON but in an other format or there are some encoding problems.

Here a new update, also added PHP_QUERY_RFC3986.

Oauth2.zip (3.1 KB)

Hi @patrick,

Thanks, below is the ouptut:

{"code":0,"file":"\/home1\/nathapb8\/public_html\/escalations\/tanzu\/dmxConnectLib\/lib\/oauth\/Oauth2.php","line":361,"message":"Syntax error - ","trace":"#0 \/home1\/nathapb8\/public_html\/escalations\/tanzu\/dmxConnectLib\/lib\/oauth\/Oauth2.php(259): lib\\oauth\\Oauth2->httpPost('https:\/\/pivotal...', Array)\n#1 \/home1\/nathapb8\/public_html\/escalations\/tanzu\/dmxConnectLib\/lib\/oauth\/Oauth2.php(230): lib\\oauth\\Oauth2->grant('authorization_c...', Array)\n#2 \/home1\/nathapb8\/public_html\/escalations\/tanzu\/dmxConnectLib\/modules\/oauth.php(24): lib\\oauth\\Oauth2->authorize(Array, Object(stdClass))\n#3 \/home1\/nathapb8\/public_html\/escalations\/tanzu\/dmxConnectLib\/lib\/App.php(193): modules\\oauth->authorize(Object(stdClass), 'sfdc_credential...')\n#4 \/home1\/nathapb8\/public_html\/escalations\/tanzu\/dmxConnectLib\/lib\/App.php(157): lib\\App->execSteps(Object(stdClass))\n#5 \/home1\/nathapb8\/public_html\/escalations\/tanzu\/dmxConnectLib\/lib\/App.php(127): lib\\App->execSteps(Array)\n#6 \/home1\/nathapb8\/public_html\/escalations\/tanzu\/dmxConnectLib\/lib\/App.php(116): lib\\App->exec(Object(stdClass), false)\n#7 \/home1\/nathapb8\/public_html\/escalations\/tanzu\/dmxConnectLib\/lib\/App.php(94): lib\\App->exec(Object(stdClass))\n#8 \/home1\/nathapb8\/public_html\/escalations\/tanzu\/dmxConnect\/api\/Escalations\/escalation_closure.php(8): lib\\App->define(Object(stdClass))\n#9 {main}"}

Still not giving any response, have updated the code to follow any redirects and it should also return the status code in the error message.

Oauth2.zip (3.2 KB)

Thanks @patrick,
we may finally have a clue now:
{"code":0,"file":"\/home1\/nathapb8\/public_html\/escalations\/tanzu\/dmxConnectLib\/lib\/oauth\/Oauth2.php","line":262,"message":"grant type not supported","trace":"#0 \/home1\/nathapb8\/public_html\/escalations\/tanzu\/dmxConnectLib\/lib\/oauth\/Oauth2.php(230): lib\\oauth\\Oauth2->grant('authorization_c...', Array)\n#1 \/home1\/nathapb8\/public_html\/escalations\/tanzu\/dmxConnectLib\/modules\/oauth.php(24): lib\\oauth\\Oauth2->authorize(Array, Object(stdClass))\n#2 \/home1\/nathapb8\/public_html\/escalations\/tanzu\/dmxConnectLib\/lib\/App.php(193): modules\\oauth->authorize(Object(stdClass), 'sfdc_credential...')\n#3 \/home1\/nathapb8\/public_html\/escalations\/tanzu\/dmxConnectLib\/lib\/App.php(157): lib\\App->execSteps(Object(stdClass))\n#4 \/home1\/nathapb8\/public_html\/escalations\/tanzu\/dmxConnectLib\/lib\/App.php(127): lib\\App->execSteps(Array)\n#5 \/home1\/nathapb8\/public_html\/escalations\/tanzu\/dmxConnectLib\/lib\/App.php(116): lib\\App->exec(Object(stdClass), false)\n#6 \/home1\/nathapb8\/public_html\/escalations\/tanzu\/dmxConnectLib\/lib\/App.php(94): lib\\App->exec(Object(stdClass))\n#7 \/home1\/nathapb8\/public_html\/escalations\/tanzu\/dmxConnect\/api\/Escalations\/escalation_closure.php(8): lib\\App->define(Object(stdClass))\n#8 {main}"}

This may be of help ?(the part where the parameters should be in the body rather than in the URL): https://salesforce.stackexchange.com/questions/172980/errorunsupported-grant-type-error-descriptiongrant-type-not-supported.

If I’m correct it is using grant_type=authorization_code, this should be correct according the oauth2 specs and the salesforce documentation.

https://help.salesforce.com/articleView?id=remoteaccess_oauth_web_server_flow.htm&type=5#request_access_token

Is there a way to see what we are sending in the post body @patrick? Maybe that will help see farther…

On line 262 it only throws the error message, we could throw the whole response instead to see the full json that was returned.

throw new \Exception(json_encode($response));

Getting the exact request how it was send is a bit more difficult.

Thanks @patrick
The output doesn’t help that much:
{"code":0,"file":"\/home1\/nathapb8\/public_html\/escalations\/tanzu\/dmxConnectLib\/lib\/oauth\/Oauth2.php","line":263,"message":"{\"error\":\"unsupported_grant_type\",\"error_description\":\"grant type not supported\"}","trace":"#0 }

A comment in this page is interesting:

I can verify that setting "Content-Type" header to "'application/x-www-form-urlencoded" addresses this issue.
Note to documentation team: might be nice if that were mentioned somewhere or anywhere since it's an absolute requirement.

I’ve tried putting it on the Oauth parameters, but that only sends it via URL parameter:
image

How could I set that content-type header and test myself, assuming wappler isn’t doing it already?
I am guessing it has something to do with the values not being url-encoded.

It uses curl for sending the request, the code is in the httpPost function (line 335). You can see the CURLOPT_POST option is set to true and CURLOPT_POSTFIELDS is used to set the post values. When the CURLOPT_POSTFIELDS is not an array it is posted as application/x-www-form-urlencoded, if the value is an array it is posted as multipart/form-data. In our case the value is always an urlencoded string, so it should always use application/x-www-form-urlencoded.

https://www.php.net/manual/en/function.curl-setopt.php

Thanks @patrick - that’s getting way beyond my competency.
All the research that I’ve done (there is quite a few cases out there with this error) point to either url encoding issues (and from your comment above, it seems that can be discarded), or not passing the right pieces in the body of the post (and that is what you mention is more difficult to extract, maybe we could put the output of the curl to a txt file in my server?).
I’m going to give it a rest tonight. Thanks for all of your help today.

I’d appreciate if we could continue putting some effort on this at your convenience in the next few days, a big chunk of my projects depends on this functionality.

Hi @patrick,

My salesforce admin has pointed out to this page: https://salesforce.stackexchange.com/questions/48119/oauth2-refresh-token-flow-unsupported-grant-type

He highliths that there is a specific step to request a refresh token, and that the code/step we are troubleshooting is working fine and is getting the access token, but what we are missing is a
grant_type=refresh_token on top of what we have. He pointed out to this documentation page: https://help.salesforce.com/articleView?id=remoteaccess_oauth_refresh_token_flow.htm&type=5

Does this make any sense?

Edit: to prove this theory, we created a new user and when running the flow for the first time, we got an access token response and the full flow worked uninterrupted. We then logged out manually of Salesforce, closed the browser and then attempted the same api call, and it failed again with the error code we’ve had.

So if I understand correctly your app is already authorized and that is why new authorization fails. This indeed make sense, you only need to authenticate once and you store the access_token and refresh_token, when the access_token is expired you use the refresh_token to request a new access_token. You normally don’t authenticate each time again.