Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refresh_token is not avaliable in response #263

Closed
navneetccna opened this issue Jul 22, 2014 · 28 comments

Comments

Projects
None yet
@navneetccna
Copy link

commented Jul 22, 2014

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"
}'

@ianbarber

This comment has been minimized.

Copy link
Contributor

commented Jul 28, 2014

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

@navneetccna

This comment has been minimized.

Copy link
Author

commented Jul 30, 2014

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

@ianbarber

This comment has been minimized.

Copy link
Contributor

commented Jul 31, 2014

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

@navneetccna

This comment has been minimized.

Copy link
Author

commented Aug 1, 2014

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();

}

@navneetccna

This comment has been minimized.

Copy link
Author

commented Aug 1, 2014

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

@pugsley

This comment has been minimized.

Copy link

commented Aug 3, 2014

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");

@ianbarber ianbarber added documentation and removed needs-repro labels Aug 4, 2014

@navneetccna

This comment has been minimized.

Copy link
Author

commented Aug 4, 2014

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

@ianbarber

This comment has been minimized.

Copy link
Contributor

commented Aug 4, 2014

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=

@navneetccna

This comment has been minimized.

Copy link
Author

commented Aug 4, 2014

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

@navneetccna

This comment has been minimized.

Copy link
Author

commented Aug 4, 2014

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 ?

@ianbarber

This comment has been minimized.

Copy link
Contributor

commented Aug 4, 2014

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

@navneetccna

This comment has been minimized.

Copy link
Author

commented Aug 4, 2014

nop

@ianbarber

This comment has been minimized.

Copy link
Contributor

commented Aug 4, 2014

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?

@navneetccna

This comment has been minimized.

Copy link
Author

commented Aug 5, 2014

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

@edmundluong

This comment has been minimized.

Copy link

commented Aug 11, 2014

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!

@ip512

This comment has been minimized.

Copy link
Contributor

commented Sep 7, 2014

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 ?

@ianbarber

This comment has been minimized.

Copy link
Contributor

commented Sep 8, 2014

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.

@ip512

This comment has been minimized.

Copy link
Contributor

commented Sep 8, 2014

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 ?

@ianbarber

This comment has been minimized.

Copy link
Contributor

commented Sep 8, 2014

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.

@ip512

This comment has been minimized.

Copy link
Contributor

commented Sep 9, 2014

I created PR #309

@ianbarber

This comment has been minimized.

Copy link
Contributor

commented Sep 17, 2014

PR merged!

@erfanimani

This comment has been minimized.

Copy link

commented Feb 20, 2016

@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?!"

@bshaffer

This comment has been minimized.

Copy link
Contributor

commented Feb 20, 2016

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!

@genebustam

This comment has been minimized.

Copy link

commented Nov 16, 2016

@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.

@dxdc

This comment has been minimized.

Copy link

commented Dec 4, 2016

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;
@webgurus

This comment has been minimized.

Copy link

commented Dec 16, 2016

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...

@jmcgranahan

This comment has been minimized.

Copy link

commented Mar 29, 2017

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

@yidas

This comment has been minimized.

Copy link

commented Apr 27, 2017

@pugsley is right!

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

$client->setAccessType('offline');
$client->setApprovalPrompt('force');
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.