At https://developers.google.com/api-client-library/php/auth/web-app#offline it says:
Access tokens periodically expire. You can refresh an access token without prompting the user for permission (including when the user is not present) if you requested offline access to the scopes associated with the token.
So, how is the developer supposed to do that? This looks like the answer:
If your application needs offline access to a Google API, set the API client's access type to offline:
$client->setAccessType("offline");
After a user grants offline access to the requested scopes, you can continue to use the API client to access Google APIs on the user's behalf when the user is offline. The client object will refresh the access token as needed.
So, the documentation is cristal-clear: apparently, you don't need to do anything, other than calling setAccessType("offline"). Apparently, the library is supposed to take care of refreshing the tokens automagically.
Well, nope.
I have tried the examples (which don't involve refreshing the tokens) and they worked. I tried storing the obtained tokens in a database and then using the token I had stored within the expiration time, and it worked.
But then I tried using the stored token after a longer time, and of course, I get the "invalid credentials" error because of the expired tokens.
Access tokens don't get automagically refreshed. If that is supposed to happen, it doesn't. More probably, there is something that we are supposed to do with the refresh_token item that is sometimes included in the responses returned by getAccessToken() (but not always, even if always configuring for offline access).
However, the docs don't mention about what it is that we should do in order to refresh a token.
Greetings! We'd be happy to help out, answer questions, and even fix bugs. I am however, going to have to ask you to monitor your language :) We're all humans here who would love to help, but please keep things respectful. We're working on getting a code of conduct in all of our repositories, but for now let this one be your guide. Thanks!
I'll try my best, it's difficult to hold back one's frustration. I don't think I have ever seen anything as poorly documented as Google's APIs and their PHP libraries.
Now, regarding the issue at hand, I have figured it out in detail.
The problem is that, when the user gets redirected back to the application after giving his consent, and the application calls
$client->authenticate($_GET['code'])
even if I ALWAYS set 'offline' access, the array returned by getAccessToken() sometimes contains a refresh_token element, sometimes it doesn't.
It seems that it only contains the refresh_token when the user grants the application access for the first time (i.e. has to click "allow"), or perhaps also when the user gets through the permissions screen a subsequent time (i.e. without having to click "allow" because he already authorized the app) if the last token has expired - but not in the other cases.
Now, to me that is plain wrong (and I'm under the impression this is the API's fault, not the library's, but I'm not 100% sure). But if this is the expected behavior _AND_ there's a good reason for this weird design, then it should definitely be documented and there should be examples.
The following workflow, which looks totally reasonable and seems to be pretty much the one suggested by the documentation (actually, the docs say very little about STORING access tokens, and in the examples there's NOTHING, so one can only infer), does not work:
getAccessToken()), retrieve it and use it (i.e. setAccessToken())$_GET['code'], exchange the code for an access token, and store it.This only works as long as:
So, you have 2 options:
refresh_token, and when you exchange a code for a token, if it does not contain a refresh_token and you do have a refresh_token stored previously, you make sure not to delete the old refresh_token, which is still valid. That is, when you obtain an array from getAccessToken() (after calling authenticate()), you must not blindly store it overriding any previous token array you had previously stored. You must make sure that, if you had one that contained a refresh_token and the new one does not contain a refresh_token, the old refresh_token is kept.Still, even in case B, which is more robust than A, you cannot afford to miss the opportunity to store the refresh_token the first time, because the next time you may not get it.
Now, you can't tell me that this is intuitive and that one is supposed to figure this out by reading the current documentation or the examples.
If authenticate() simply always fetched a refresh_token together with the access token (as long as the access type is offline), regardless of whether it's the first login or not, one wouldn't have to worry about any of this.
Hi @teo1978! I think the missing piece of the puzzle is that if you use access type offline and you've already gotten a refresh token at some point in the past, the authorization service will only give you an access token. You need to do prompt='consent' (which is documented somewhere in this long page).
And yes, in general you need to keep the access token given as long as possible - basically, keep it until it fails to get a new access token - then, mark it as invalid and prompt the user again.
I'm going to go ahead and close this, but if you need any more help please feel free to comment.
Hi @teo1978! I think the missing piece of the puzzle is that if you use access type offline and you've already gotten a refresh token at some point in the past, the authorization service will only give you an access token
Exactly, that's the missing piece of the puzzle.
So why do you close the issue without adding the missing piece to the documentation?
This behavior is far from obvious (indeed it's plain wrong, there's no valid reason why the authorization srvice shouldn't give you a refresh token every time - but I guess that's the API's fault, not the library's) and hence should be thoroughly documented.
Can documentation be contributed to? It is obvious it is left behind as it is still using authenticate instead of fetch... methods
Hey Google: closing this ticket without updating the documentation is VERY BAD. Do you care, even a little bit, about the programmers that are using your APIs? Then please give us BETTER documentation. Thanks.
Do you care, even a little bit, about the programmers that are using your APIs?
Nope, it's pretty obvious they don't.
@theacodes as per #issuecomment-401640922 please reopen.
Just joined the queue. :(
Guys, I found this helpful - https://github.com/googleapis/google-api-php-client/issues/998#issuecomment-233864103
can you explain how you get them in the first place
@ib01 please open a new issue with your question, including as detailed a code sample as possible.
Here is a bit outdated but still very good example on configuring auth using PHP.
https://www.domsammut.com/code/php-server-side-youtube-v3-oauth-api-video-upload-guide
Also, to note there is a new function in Client.php you can use to refresh the access key if it's expired.
public function fetchAccessTokenWithRefreshToken($refreshToken = null)
Hey folks,
First, an apology: I've been on medical leave for a while and drowning in GitHub issues long before that. This slipped under my radar.
I've submitted an internal change to update the documentation here to include the prompt bits. The new sample code will look like this:
$client = new Google_Client();
$client->setAuthConfig('client_secret.json');
$client->addScope({{ oauth2_scope_php }});
$client->setRedirectUri('http://' . $_SERVER['HTTP_HOST'] . '/oauth2callback.php');
// offline access will give you both an access and refresh token so that
// your app can refresh the access token without user interaction.
$client->setAccessType('offline');
// Using "force" ensures that your application always receives a refresh token.
// If you are not using offline access, you can omit this.
$client->setApprovalPrompt("force");
$client->setIncludeGrantedScopes(true); // incremental auth
Expect the page to be updated in the new few days.
Thank you! Hope you are feeling better now :)
Guys I really can't believe the way this has been dealt with. It needs to be made really clear - the entire thing just falls over silently and the user has no idea how to resolve things.
If there is no refresh_token in the returned token then do NOT save or set the token. On the next authorization cycle it will be set - so basically if it is not set, do not save.
This needs to be made abundantly clear as there is a lot angst building towards google for the poor nature of documentation AND implementation of their API's.
Most helpful comment
Hey folks,
First, an apology: I've been on medical leave for a while and drowning in GitHub issues long before that. This slipped under my radar.
I've submitted an internal change to update the documentation here to include the
promptbits. The new sample code will look like this:Expect the page to be updated in the new few days.