Google-api-php-client: refresh_token is not avaliable in response

Created on 22 Jul 2014  路  28Comments  路  Source: googleapis/google-api-php-client

using following code to get token but there is no refresh token available in json

$client->setAccessType("offline");
$client->authenticate($_GET['code']);
$client->setApprovalPrompt("auto");
$_SESSION['upload_token'] = $client->getAccessToken();

print_r($_SESSION['upload_token']);

output :

{"access_token":"ya29.PgB0miRDMD3H-iAAAAD88eQLK9N84muCYYR4TEhwffsj5yNJBXMt34Sd6B815Q","token_type":"Bearer","expires_in":3597,"created":1404986763}

i am trying to refresh token for my app i want to show google drive list but after login and connect to the drive after some time token expired and unable to refresh the token

i want to save the token in database and use it for next time so user can access his google drive any time after connection it.

if i try following code

$client->refreshToken($google_token);

getting following error

Error refreshing the OAuth2 token, message: '{
"error" : "invalid_grant"
}'

triage me

Most helpful comment

@ianbarber, good article on why not to use approval_prompt=force. The reason why people do this, I'm guessing, is because almost all of the PHP code samples found on the developers.google.com don't actually save the refresh token when refreshing the access token.

Example: https://developers.google.com/apps-script/guides/rest/api

// Refresh the token if it's expired.
if ($client->isAccessTokenExpired()) {
    $client->refreshToken($client->getRefreshToken());
    file_put_contents($credentialsPath, $client->getAccessToken());
}

Above sample only saves the new access token (which doesn't contain the refresh token) and nowhere is explained that you'll need to save the refresh token separately, otherwise you'll lose it after the first time your access token is expired.

The code sample basically works for the first two access tokens, then it'll stop working.

Something like this would have been better:

    if ($client->isAccessTokenExpired()) {
        $refreshToken = $client->getRefreshToken();
        $client->refreshToken($refreshToken);
        $newAccessToken = $client->getAccessToken();
        $newAccessToken['refresh_token'] = $refreshToken;
        file_put_contents($credentialsPath, json_encode($newAccessToken));
    }

This is probably a frequent cause of "Dang, why did it lose my refresh token? Again?!"

All 28 comments

Are you calling $client->setAccessType("offline"); before you generate the auth url? That parameter needs to be passed at that point, not when exchanging.

yes i am doing it before the generating the auth url.

Could you put together a minimal code sample that demonstrates the problem? I can't reproduce it here at the moment.

this is my code every thing is working fine for 1-2 hours but after that my token get expired and i am unable to refresh that as i mention previously.

include_once('../../../functions/drives_connections.php');

$api_connections=new Api_Connections();
$apidetails=$api_connections->api_connections();
$client_details=$apidetails["google_api"];

$client_id = $client_details["client_id"];
$client_secret = $client_details["client_secret"];
$redirect_uri = $client_details["redirect_uri"];

/***************
ATTENTION: Fill in these values! Make sure
the redirect URI is to this page, e.g:
http://localhost:8080/fileupload.php
***************/

$client = new Google_Client();
$client->setClientId($client_id);
$client->setClientSecret($client_secret);
$client->setRedirectUri($redirect_uri);
$client->addScope("https://www.googleapis.com/auth/drive");
$client->setAccessType("offline");
$service = new Google_Service_Drive($client);

if (isset($_REQUEST['logout'])) {
unset($_SESSION['upload_token']);
}

if (isset($_GET['code'])) {
$client->authenticate($_GET['code']);

$client->setApprovalPrompt("auto");

$_SESSION['upload_token'] = $client->getAccessToken();

}

can you please provide a working example for the same in this repo with other examples ?

I've found the only way to get this to work as you're intending is to set approval prompt to force. Automatic approval prompts never return refresh tokens (even when offline is set).

$client->setApprovalPrompt("force");

ok let me try $client->setApprovalPrompt("force"); this with my code.

Ah, that explains it! Pugsley: you don't need force every time, only when you don't have a refresh token stored on the server.

Basically, with Auto it will only show the consent dialog when the user has not previously given there consent. You will only get a refresh token if the consent dialog is shown. Therefore, when you get a refresh token it should be put into permenant storage like a database - you can look up the user using an api call from the access token or via ID token data. So, force causes the dialog to be shown every time, which means a refresh token every time, but that means you are churning refresh tokens, and you are giving the user a worse experience. Take a look at http://www.riskcompletefailure.com/2013/12/are-you-using-approvalpromptforce.html for a bit more on that.

Clearly this is not very obvious, so what I'll do is add a note to the documentation highlighting this!

navneetccna: to test this, try revoking your access, then signing in again. You can revoke through your account settings, or by getting an access token after you sign in and calling this URL:
https://accounts.google.com/o/oauth2/revoke?token=

can you please provide a working example for the same in this repo with other examples ?

As i try and change $client->setApprovalPrompt("auto"); to $client->setApprovalPrompt("force"); still didnt receive any refresh token in response

{"access_token":"ya29.VwDJ4UGbKTKsyRwAAADusG-fltPrvhT_RtbHA5uiLLLU0pmXzerpqwkOSrv0CQ","token_type":"Bearer","expires_in":3598,"created":1407152019}

can you please provide a working example for the same in this repo with other examples ?

To confirm, did you see the consent screen once you changed it to force?

nop

Possibly it wasn't set before generating the auth URL then. Could you try revoking a (valid, not expired) access token, and then try again?

yes i tried that too also i removed app from user account and tried this again

I can confirm that after struggling with getting a refresh token, forcing the approval prompt and setting the access type to offline is sufficient to solve the issue. I did have to revoke the previous authorization to get this working as well.

Something as such (I extended the Google_Client class for my implementation):
// ignore the $this-> values
$this->setClientId($this->_clientID);
$this->setClientSecret($this->_clientSecret);
$this->setRedirectUri($this->_redirectURI);
$this->setDeveloperKey($this->_devKey);
$this->setScopes(array('https://www.googleapis.com/auth/analytics.readonly'));
$this->setAccessType('offline');
$this->setApprovalPrompt('force');

Keep up the good work guys!

I use the same code and for my application I need to autorize it twice. One time to get authorized for access, and another time to get offline access and retrieve refresh token.
Is there a way to ask both permissions at the same time ?

Both tokens come back at the same time - you'll get an access token for use immediately, and a refresh token for use in the future. Just request offline access and be aware that you only get a refresh token the first time (effectively, whenever the user sees a consent screen) - so you should store the refresh part against the user, and in future sign ins look it up.

Actually I was confused by the message that is displayed when user authorize the application. The first time, nothing is displayed about offline access, but the refresh_token is well returned. If I call the authorize URL another time with forced promt, the message displayed show explicitly an offline access request (even if the access is already granted).
This looks weird, but it works fine if I test the presence of resfresh_token. By the way, I use json_decode($client->getAccessToken()) to get the refresh token. Is there another way to check if the refresh token is available ?

That message is due to incremental auth. Take a look at http://www.riskcompletefailure.com/2013/12/are-you-using-approvalpromptforce.html for more on that.

Re getting the token - nope, you're doing it the right way. It might be nice to have an accessor method on Google_Client though, feel free to send a PR or open an issue for that.

I created PR #309

PR merged!

@ianbarber, good article on why not to use approval_prompt=force. The reason why people do this, I'm guessing, is because almost all of the PHP code samples found on the developers.google.com don't actually save the refresh token when refreshing the access token.

Example: https://developers.google.com/apps-script/guides/rest/api

// Refresh the token if it's expired.
if ($client->isAccessTokenExpired()) {
    $client->refreshToken($client->getRefreshToken());
    file_put_contents($credentialsPath, $client->getAccessToken());
}

Above sample only saves the new access token (which doesn't contain the refresh token) and nowhere is explained that you'll need to save the refresh token separately, otherwise you'll lose it after the first time your access token is expired.

The code sample basically works for the first two access tokens, then it'll stop working.

Something like this would have been better:

    if ($client->isAccessTokenExpired()) {
        $refreshToken = $client->getRefreshToken();
        $client->refreshToken($refreshToken);
        $newAccessToken = $client->getAccessToken();
        $newAccessToken['refresh_token'] = $refreshToken;
        file_put_contents($credentialsPath, json_encode($newAccessToken));
    }

This is probably a frequent cause of "Dang, why did it lose my refresh token? Again?!"

Thank you for your comment. Yes, approval_prompt=force is not encouraged. But it is no longer necessary to do the check you have above.

The authorize method checks if the refresh token exists. The developer just needs to make sure when they pass in an access token, it includes the refresh token.

The sample you are linking to is referring to v1 of this library, and will be updated very soon. It would be great to get your feedback on it!

@erfanimani you sir are a gentleman and a scholar.

That was the exact reason why it wasn't working. And I'm sure it's the reason why it wasn't working for @navneetccna either. Those code examples should be updated by Google.

Google has updated their PHP quickstart, with an improved method to handle this:

https://developers.google.com/apps-script/guides/rest/quickstart/php

Snippet below:

  // Load previously authorized credentials from a file.
  $credentialsPath = expandHomeDirectory(CREDENTIALS_PATH);
  if (file_exists($credentialsPath)) {
    $accessToken = json_decode(file_get_contents($credentialsPath), true);
  } else {
    // Request authorization from the user.
    $authUrl = $client->createAuthUrl();
    printf("Open the following link in your browser:\n%s\n", $authUrl);
    print 'Enter verification code: ';
    $authCode = trim(fgets(STDIN));

    // Exchange authorization code for an access token.
    $accessToken = $client->fetchAccessTokenWithAuthCode($authCode);

    // Store the credentials to disk.
    if(!file_exists(dirname($credentialsPath))) {
      mkdir(dirname($credentialsPath), 0700, true);
    }
    file_put_contents($credentialsPath, json_encode($accessToken));
    printf("Credentials saved to %s\n", $credentialsPath);
  }
  $client->setAccessToken($accessToken);

  // Refresh the token if it's expired.
  if ($client->isAccessTokenExpired()) {
    $client->fetchAccessTokenWithRefreshToken($client->getRefreshToken());
    file_put_contents($credentialsPath, json_encode($client->getAccessToken()));
  }
  return $client;

Hey guys,
I have a huge problem where absolutely no error is returned from google nor php. I've written a google client oauth2 connect model, which is perfectly working when I'm running it on my dev environment on my mac (I'm not using a simple localhost, it's actually vagrant what I'm using to mirror the production server as much as possible) but it doesn't work when I push it to production. It's just simply not returning any error. The code reaches the part where I have the auth code from Google and it hangs at the part where I'm trying to exchange it for a token ( $client->authenticate($auth_code) ). I really don't know what am I doing wrong, since everything is working perfect without errors on dev site (which has a public link by the way, and I tried accessing it from different public IP addresses). The moment I push the code to a live server, it just stops authenticating. I have the correct oauth credentials set up for every production server, redirect uri is correct. I simply don't know what am I doing wrong...

I am having this same error as previous users, even with the modified "refresh token". I even deleted my directory and started over and got the same error so I don't know what to do next. Can anyone give me guidance? Here's the error (and this is without making any modifications to the file quickstart.php):

Fatal error: Uncaught LogicException: refresh token must be passed in or set as part of setAccessToken in C:\Users\mcgranj\Dropbox\eBay_web\google\vendor\google\apiclient\src\Google\Client.php:258 Stack trace: #0 C:\Users\mcgranj\Dropbox\eBay_web\google\quickstart.php(69): Google_Client->fetchAccessTokenWithRefreshToken(NULL) #1 C:\Users\mcgranj\Dropbox\eBay_web\google\quickstart.php(98): getClient() #2 {main} thrown in C:\Users\mcgranj\Dropbox\eBay_web\google\vendor\google\apiclient\src\Google\Client.php on line 258

@pugsley is right!

In PHP SDK, adding $client->setApprovalPrompt("force") will work for Token refresh.

$client->setAccessType('offline');
$client->setApprovalPrompt('force');
Was this page helpful?
0 / 5 - 0 ratings

Related issues

armetiz picture armetiz  路  4Comments

Romain picture Romain  路  4Comments

Fredyy90 picture Fredyy90  路  3Comments

cmcfadden picture cmcfadden  路  5Comments

ghost picture ghost  路  4Comments