QuickBooks-V3-PHP-SDK icon indicating copy to clipboard operation
QuickBooks-V3-PHP-SDK copied to clipboard

Refresh OAuth 2 Access token with Refresh Token failed

Open mishabachkur opened this issue 4 years ago • 13 comments

I often get this error - Refresh OAuth 2 Access token with Refresh Token failed. Body: [{"error":"invalid_grant"}]

mishabachkur avatar Jun 02 '20 05:06 mishabachkur

@mishabachkur This error typically occurs when you have an incorrect configuration for clientId, clientSecret etc. Can you please check you have configure the keys accurately?

bsivalingam avatar Jun 06 '20 01:06 bsivalingam

The issue: confirmed

The reason:

With the same credentials:

        $companyInfo = $dataService->getCompanyInfo();
        return json_encode($companyInfo, true);

worked properly

But,

        $OAuth2LoginHelper = $dataService->getOAuth2LoginHelper();
        $accessTokenObj    = $OAuth2LoginHelper->exchangeAuthorizationCodeForToken(
            self::QUICKBOOKS_AUTHORIZATION_CODE,
            self::QUICKBOOKS_REALM_ID
        );

Provided with an issue:

  Cron-job "socalgraph_quickbooks" threw exception QuickBooksOnline\API\Exception\ServiceException  
                                                                                                    

                                                                                                                               
  [QuickBooksOnline\API\Exception\ServiceException]                                                                            
  Exchange Authorization Code for Access Token failed. Body: [{"error_description":"Token invalid","error":"invalid_grant"}].  

coresh avatar Jul 13 '20 12:07 coresh

@coresh That is very interesting find. Thanks for persisting on the issue. Would you be able to send me the raw request and response log for both the calls? I can bring it to my downstream team.

bsivalingam avatar Jul 13 '20 16:07 bsivalingam

Executed code:

        $dataService          = DataService::Configure(array(
            'auth_mode'       => 'oauth2',
            'ClientID'        => self::QUICKBOOKS_CLIENT_ID,
            'ClientSecret'    => self::QUICKBOOKS_CLIENT_SECRET,
	    'RedirectURI'     => self::QUICKBOOKS_REDIRECT_URI,
            'scope'           => "com.intuit.quickbooks.accounting",
            'baseUrl'         => "Production"
        ));

        $OAuth2LoginHelper = $dataService->getOAuth2LoginHelper();

	$accessTokenObj = $OAuth2LoginHelper->exchangeAuthorizationCodeForToken(
	    $this->authorizationCode, self::QUICKBOOKS_REALM_ID
	);

        $error = $dataService->getLastError();

	if ($error) {
	    echo "The Status code is: " . $error->getHttpStatusCode() . "\n";
	    echo "The Helper message is: " . $error->getOAuthHelperError() . "\n";
	    echo "The Response message is: " . $error->getResponseBody() . "\n";
	    exit();
	}

vendor/quickbooks/v3-php-sdk/src/Core/OAuth/OAuth2/OAuth2LoginHelper.php

public function exchangeAuthorizationCodeForToken($code, $realmID){
...
var_dump(http_build_query($parameters));
var_dump($intuitResponse->getBody());
var_dump($intuitResponse->getHeaders());
...

Result:

string(171)
grant_type=authorization_code
code=real_code
redirect_uri=https%3A%2F%2Fdeveloper.intuit.com%2Fv2%2FOAuth2Playground%2FRedirectUrl


string(61) "{"error_description":"Token invalid","error":"invalid_grant"}"

array(10) {
  ["date"]=>
  string(29) "Mon, 13 Jul 2020 16:24:34 GMT"
  ["content-type"]=>
  string(30) "application/json;charset=utf-8"
  ["content-length"]=>
  string(2) "61"
  ["server"]=>
  string(5) "nginx"
  ["strict-transport-security"]=>
  string(16) "max-age=15552000"
  ["intuit_tid"]=>
  string(35) "1-5f0c8ac2-94777b4a0a0f25100de4b6f2"
  ["x-spanid"]=>
  string(38) "6f58449e-aa9e-4cf7-b640-8ec0744cc449:1"
  ["x-amzn-trace-id"]=>
  string(81) "Self=1-5f0c8ac2-c63f2990c703c9bc452205fc;Root=1-5f0c8ac2-94777b4a0a0f25100de4b6f2"
  ["cache-control"]=>
  string(18) "no-cache, no-store"
  ["pragma"]=>
  string(8) "no-cache"
}

coresh avatar Jul 13 '20 16:07 coresh

@coresh Ah I see what you are saying. These calls use different auth headers. The auth code call uses Basic XXXX where XXXX is a base 64 encoded value of client id and secret. The getUserInfo call is a Bearer token call, where you use the accessToken which you had last obtained. I would still suggest checking the value for clientId that is being passed in the request. Since the client information are secure, you could choose to create a support ticket and we can help you there.

bsivalingam avatar Jul 14 '20 01:07 bsivalingam

Re : I would still suggest checking the value for clientId that is being passed in the request.

clientID -> it's Ok.

The reason:

call:

$dataService->getCompanyInfo()

returns properly output

Please check concerning to the Production environment.

Thank you

coresh avatar Jul 14 '20 14:07 coresh

@coresh can you please elaborate what you mean by "Please check concerning to the Production environment."?

bsivalingam avatar Jul 16 '20 20:07 bsivalingam

I am getting the same error. I have found that I get a new refresh token before 24 hours have elapsed since I received the old one. My application is configured to save the new token every time, but the new token does not work. If I log the old token, and use that to call the refresh token endpoint again it works fine, and I get the old token back. I do believe there might be an issue on your guy's end.

Checking my error logs I notice that I can't re-authenticate my server about 8 hours after doing so manually, leading me to believe that that's when you guys return a new refresh token to me.

My client ID, and secret are both correct, as if I go to my server and simply change the refresh token back to what it was before the automated process I can retrieve a new access token, but a new refresh token is not returned.

This is confusing so I'll go through step by step what I did, and how I think things went on the server:

  1. use the playground to retrieve an access and refresh token
  2. log both tokens at the time i receive them in my issue tracker
  3. save those on the server, and begin the re-authentication job (this runs every 15 minutes to retrieve a new access token)
  4. 8 hours later, on the server: quickbooks returns a new refresh token, earlier than expected, but it's saved anyway, as recommended by the documentation at point 3 here
  5. that new token does not work, leading to the invalid_grant error from quickbooks
  6. I notice the error (at the time of writing, roughly 22 hours after I initiated step 1), log into the server, with the old refresh token, which I logged earlier, and replace the new refresh token I got from quickbooks with the old one
  7. call the refresh_token endpoint, with the old token, which returns a new access_token as expected, and the old refresh token.

I think that around 8 hours after I initiated step 1, you guys return a new refresh token to me.

edit: I put the old token back on my server, and was able to proceed to receive a new access token using the old token (it hadn't expired as yet, still had two hours left on it). Two hours later, that refresh token expired, and I was not given a new one by quickbooks. I tried the new one I got earlier, and that doesn't work either.

daraul avatar Sep 10 '20 11:09 daraul

@daraul Have you figure out how to solve this?

radenkozec avatar Oct 08 '21 07:10 radenkozec