0
votes

I am working on the last step of the OAuth dance with Intuit.

This is a Delphi XE2 desktop app and I am using the Indy components.

I am successfully getting my request token from Intuit and the callback from their website is properly redirecting me to our web service after I authorize my app.

Using the returned verifier from Intuit I build my request to get the access token.

I am always getting back a HTTP/1.1 401 Unauthorized error when I make my call to Intuit. If I post the URL I build into Chrome browser I get a "oauth_problem=signature_invalid" message.

Obviously I don't have the fullest grasp of the OAuth thingy but I am pretty darn sure my signature is correct. I use the signature checking tool from Google here: http://oauth.googlecode.com/svn/code/javascript/example/signature.html and my signature always matches. My signature base matches as well.

Here are the keys/values I am using to generate my signature and the URL I am generating from the result:

http method: GET
URL: https://oauth.intuit.com/oauth/v1/get_access_token
singing method: HMAC-SHA1
version: 1.0
ConsumerKey: qyprdGItCTwCAbvx3qNSJR5vDnCOVX
ConsumerSecret: 7IxtFa7aYU2O0PvMGSKsEqaZRvQFoZFaPCtNqg2D
RequestToken: qyprdsHZZft4b2Xskx7dMh1qKOEDw6hrns2Dh7KhN7H8csZt
TokenSecret: AWYbBHrhYhUTYlD1kdPRal2NXRwWMo8qDZ7HDBTQ
TimeStamp: 1392232483
nonce: 3EE3F797E97810F1F0CDF866BEB1AA68

If I plug all the above into Google's signature tool I get the same signature I got from my code. My signature base matches what I get from Google's tool also.

Signature Base: GET&https%3A%2F%2Foauth.intuit.com%2Foauth%2Fv1%2Fget_access_token&oauth_consumer_key%3DqyprdGItCTwCAbvx3qNSJR5vDnCOVX%26oauth_nonce%3D3EE3F797E97810F1F0CDF866BEB1AA68%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1392232483%26oauth_token%3DqyprdsHZZft4b2Xskx7dMh1qKOEDw6hrns2Dh7KhN7H8csZt%26oauth_version%3D1.0
signature : F7m3ii6is226/waoA5wkhrFN/Ak=
signature URL encoded: F7m3ii6is226%2FwaoA5wkhrFN%2FAk%3D

Pasting the URL I build from my code below into Chrome browser give me the oauth_problem error.

URL:  https://oauth.intuit.com/oauth/v1/get_access_token?oauth_consumer_key=qyprdGItCTwCAbvx3qNSJR5vDnCOVX&oauth_token=qyprdsHZZft4b2Xskx7dMh1qKOEDw6hrns2Dh7KhN7H8csZt&oauth_signature_method=HMAC-SHA1&oauth_signature=F7m3ii6is226%2FwaoA5wkhrFN%2FAk%3D&oauth_timestamp=1392232483&oauth_nonce=3EE3F797E97810F1F0CDF866BEB1AA68&oauth_version=1.0&oauth_verifier=dv9xx1q

Keep in mind my code to generate my signature works fine when building the URL for the get_request_token. It's just this last bit to get the all-important access token and access token secret where things are falling apart for me.

I read in the oauth.net documentation (http://oauth.net/core/1.0a/#auth_step3) that this last call should be done via HTTP POST but I see nothing that describes what the post body should contain and nothing on the Intuit Partner Platform describes the POST body either so I am trying a GET instead. I've tried a POST with an empty body with no luck either.

Any advice is appreciated.

Cheers! TJ

1
TJ...just curious: did you ever figure this out? My next project may be a desktop connection to QB Online via the IPP interfaces, so OAuth is a burning question... - Mark Wilsdorf
@Mark - Sadly, no. I am convinced that the implementation of the code used by Intuit has something a little odd in it that does not do the signing the same as I expect it to. They are doing the translation from request tokens to access tokens via website code. We did some trickery that allowed us to store the secret on our web service and use the same website code dll as Intuit on our web server to do the translation from request token/secret to access token/secret. It works but is not pretty. PM or email me if you need details. - TJ Asher

1 Answers

0
votes

When you enter a URL into a browser and let it go, it performs a GET request. But like you mentioned, and what the Intuit documentation you linked to says, the get_access_token request is supposed to be done using a POST request instead.

If you read section 5.2 of Intuit's documentation, it says:

OAuth Protocol Parameters are sent from the Consumer to the Service Provider in one of three methods, in order of decreasing preference:

  1. In the HTTP Authorization header as defined in OAuth HTTP Authorization Scheme.
  2. As the HTTP POST request body with a content-type of application/x-www-form-urlencoded.
  3. Added to the URLs in the query part (as defined by RFC3986 section 3).

You are doing #3 when you should be doing #2 instead. The correct POST request would look something like this:

POST /oauth/v1/get_access_token HTTP/1.1
Host: oauth.intuit.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 307

oauth_consumer_key=qyprdGItCTwCAbvx3qNSJR5vDnCOVX&oauth_token=qyprdsHZZft4b2Xskx7dMh1qKOEDw6hrns2Dh7KhN7H8csZt&oauth_signature_method=HMAC-SHA1&oauth_signature=F7m3ii6is226%2FwaoA5wkhrFN%2FAk%3D&oauth_timestamp=1392232483&oauth_nonce=3EE3F797E97810F1F0CDF866BEB1AA68&oauth_version=1.0&oauth_verifier=dv9xx1q

You mention that you are using Indy, so such a POST request can be sent using code like this:

var
  Params: TStringList;
begin
  Params := TStringList.Create;
  try
    Params.Values['oauth_consumer_key'] := 'qyprdGItCTwCAbvx3qNSJR5vDnCOVX';
    Params.Values['oauth_token'] := 'qyprdsHZZft4b2Xskx7dMh1qKOEDw6hrns2Dh7KhN7H8csZt';
    Params.Values['oauth_signature_method'] := 'HMAC-SHA1';
    Params.Values['oauth_signature'] := 'F7m3ii6is226/waoA5wkhrFN/Ak='; // <-- pay attention here!
    Params.Values['oauth_timestamp'] := '1392232483';
    Params.Values['oauth_nonce'] := '3EE3F797E97810F1F0CDF866BEB1AA68';
    Params.Values['oauth_version'] := '1.0';
    Params.Values['oauth_verifier'] := 'dv9xx1q';

    IdHTTP.Post('https://oauth.intuit.com/oauth/v1/get_access_token', Params);
  finally
    Params.Free;
  end;
  ...
end;

Take particular note of the oauth_signature value. What gets sent to the server must be url-encoded:

F7m3ii6is226%2FwaoA5wkhrFN%2FAk%3D

By default, TIdHTTP will url-encode the TStringList data for you when preparing the POST request, so do not put url-encoded data in the TStringList:

F7m3ii6is226/waoA5wkhrFN/Ak=

However, if you want to put url-encoded data in the TStringList, you will have to disable the hoForceEncodeParams flag in the TIdHTTP.HTTPOptions property. TIdHTTP will still send the TStringList data as name-value pairs with & characters in between each pair, but each pair's name and value will be sent as-is without being url-encoded by TIdHTTP.