I have a problem with the refresh tokens google returns. I think they expire after some days. Documentation says that they do not unless the user does this manually.
Here is what I do:
I prompt the user to authorize my app using Google_Client
$client = new Google_Client();
$client->setClientId(GOOGLE_CLIENT_ID);
$client->setClientSecret(GOOGLE_CLIENT_SECRET);
$client->setRedirectUri(GOOGLE_REDIRECT_URI);
$client->setScopes(array(GOOGLE_SCOPE_A,'https://www.googleapis.com/auth/userinfo.email'));
$client->setApplicationName(GOOGLE_APPLICATION_NAME);
$client->setAccessType('offline');
$client->setApprovalPrompt('force');
I store the refresh token in the database using
$refreshToken = $client->getRefreshToken();
I have a cURL that runs every day and pulls some data from analytics, it then connects using the following code:
$client = new Google_Client();
$client->setClientId(GOOGLE_CLIENT_ID);
$client->setClientSecret(GOOGLE_CLIENT_SECRET);
$client->refreshToken($refreshToken);
$client->isAccessTokenExpired() //this returns false;
Everything works fine for the first 15-30 days. After that, it looks like the refresh token is not working any more.
$client->isAccessTokenExpired() //this returns true;
and when I am trying to pull data from analaytics I am getting a Google_Service_Exception with code:401 and the message:"Login Required"
What I do then is prompt the user to authorize my app again, update the refresh token with the new one in the database and everything is working as expected for the next 15-30 days until the above problem happens again.
Does anyone have any idea why this is happening and how can I prevent this?
Thank you.
Related
Having a problem with the refreshToken procedure with Google API, upon an expired token is experienced the refreshToken doesn't get an AccessToken. I've already saved the refresh token to the db at this point.
$client = new Google_Client();
$client->setApplicationName("APP NAME");
$client->setClientId($client_id);
$client->setClientSecret($client_secret);
$client->setAccessType('offline');
$client->setScopes(array('https://www.googleapis.com/auth/calendar'));
$client->setAccessToken( json_encode($access_token_from_db) );
if ( $client->isAccessTokenExpired() ) :
$client->refreshToken($refresh_token_from_db);
$new_access_token = $client->getAccessToken();
print_r($access_token);
//save to db -- but this fails
endif;
After running this the new access token is still empty. Even if I setAccessToken to the now expired token prior to checking expiration the same problem persists.
There doesn't seem to be a straight forward example on a state after a refreshToken has already been saved.
In my application we would cron job to send updates to a user's calendar based on actions (if necessary to understand the application purpose).
The reason this answer doesn't seem correct to me:
How to refresh token with Google API client?
is because it's not using the $client->isAccessTokenExpired() method, plus isn't a clean solution, the script should be very minimal and small to complete the task. In theory:
setAccessToken to the one given on authorization
if expired (via function), use the refresh to access a new token
save the token and continue
While the above example had similar functions it's a 2013 answer and may work but isn't the best way to do this I believe.
My failure was because I messed up my own clientID, but I'm posting this script here anyways because it took me forever to find the correct answer that was small, efficient and effective (the below is strictly after a token and refresh have been already saved to your db):
$client = new Google_Client();
$client->setApplicationName('APP NAME');
$client->setClientId($client_id);
$client->setClientSecret($client_secret);
$client->setAccessType('offline');
$client->setScopes(array('https://www.googleapis.com/auth/calendar'));
// my app is for google calendar.
$access_token_from_db = 'XXXXXX';
$refresh_token_from_db = 'XXXXX';
$_tokenArray['access_token'] = $access_token_from_db
$client->setAccessToken( $_tokenArray );
//check if token expired:
if ( $client->isAccessTokenExpired() ) :
$client->refreshToken($refresh_token_from_db);
$new_access_token = $client->getAccessToken();
//now save your new access token to your db
endif;
In my website i have an upload button for upload files to google drive via api.
Here is my code:
$auth_code = GOOGLEDRIVE_AUTH_CODE;
$access_token = GOOGLEDRIVE_ACCESS_TOKEN;
$refresh_token = GOOGLEDRIVE_REFRESH_TOKEN;
$client_id = 'Google_App_Client_ID';
$client_secret = 'Google_App_Client_Secret';
$redirect_uri = 'Redirct_Url';
$client = new Google_Client();
$client->setClientId($client_id);
$client->setClientSecret($client_secret);
$client->setRedirectUri($redirect_uri);
$client->setAccessType('offline');
$client->setApprovalPrompt('force');
$client->addScope("https://www.googleapis.com/auth/drive");
$service = new Google_Service_Drive($client);
if (isset($access_token) && $access_token) {
$client->setAccessToken($access_token);
if ($client->isAccessTokenExpired()) {
$refresh_token = $client->getRefreshToken();
$client->refreshToken($refresh_token);
$access_token = $client->getAccessToken();
$co->save('GDRIVE_ACCESS_TOKEN',$access_token);
$co->save('GDRIVE_REFRESH_TOKEN',$refresh_token);
}
} else {
$authUrl = $client->createAuthUrl();
}
this line throws an error
$client->refreshToken($refresh_token);
"Error refreshing the OAuth2 token, message: '{ "error" : "invalid_grant" }'"
Reading up on the error message it sounds like the token refresh isn’t working all of a sudden. Like I said, this upload tool has been working fine for months.
Any Idea ?
Thanks,
Midhun
Invalid_grant
Your server’s clock is not in sync with NTP. (Solution: check the server time if its incorrect fix it. )
If its not that then there is no fix besides asking the user to authenticate again. Possible causes for the refresh token to have expired.
The user has revoked your access.
the refresh token hasn't been used in six months to request a new access token.
The refresh token limit has been exceeded. Applications can request multiple refresh tokens. For example, this is useful in situations where a user wants to install an application on multiple machines. In this case, two refresh tokens are required, one for each installation. When the number of refresh tokens exceeds the limit, older tokens become invalid. If the application attempts to use an invalidated refresh token, an invalid_grant error response is returned. The limit for each unique pair of OAuth 2.0 client and is 25 refresh tokens (note that this limit is subject to change). If the application continues to request refresh tokens for the same Client/Account pair, once the 26th token is issued, the 1st refresh token that was previously issued will become invalid. The 27th requested refresh token would invalidate the 2nd previously issued token and so on.
I want to build a script that will check a Authenticated User's Google Calendar via the Google Calendar PHP Client. I was able to build a simple page that lets a user Auth and give permission to the Calendar Events. I receive a token and then grab a 15 upcoming events via:
$googleCal = new Google_Service_Calendar($googleClient);
$results = $googleCal->events->listEvents($calendarId, $optParams);
But what I'm struggling with is how to save this so I can have a script check this everyday to see if there were new events added. I think what I have is close just struggling to get over the finish line.
Thanks!
--
Update, I'm trying to use the refresh token, here is my code:
public function checkRedirectCode()
{
if(isset($_GET['code']))
{
$this->client->authenticate($_GET['code']);
// $this->setToken($this->client->getRefreshToken());
$this->setToken($this->client->getAccessToken());
$this->storeUser($this->getPayload());
return true;
}
return false;
}
public function setToken($token)
{
$_SESSION['access_token'] = $token;
$this->client->setAccessToken($token);
}
I have been able to echo the refresh token so I know I'm getting a proper refresh token but I'm getting errors whenever I use the commented out string. Any ideas?
To enable your script to be called beyond the lifetime of the original access token (which only last an hour), you will need to retrieve and store the refresh token during the initial authorisation, and then use this to generate a new access token each time your script runs.
Access tokens have limited lifetimes. If your application needs access
to a Google API beyond the lifetime of a single access token, it can
obtain a refresh token. A refresh token allows your application to
obtain new access tokens.
https://developers.google.com/identity/protocols/OAuth2#basicsteps (4. Refresh the access token, if necessary.)
After the user has authenticated your app, they are returned to your redirect URI with the code query-string.
The following is an example of authenticating and getting the refresh token (this uses Analytics, but should be the same for other services):
$client = new Google_Client();
$client->setClientId('xxx');
$client->setClientSecret('xxx');
$client->setRedirectUri('xxx');
//authenticate with the code returned from google
$authenticate = $client->authenticate($_GET['code']);
//get the refresh token
$refreshToken = $client->getRefreshToken();
//store refresh token in database...
Then, when you run your daily script, use the refresh token (retrieved from your database) to generate a new access token:
$client = new Google_Client();
$client->setClientId('xxx');
$client->setClientSecret('yyy');
$client->addScope(Google_Service_Analytics::ANALYTICS_READONLY);
$client->refreshToken($user->refresh_token);
$newToken = $client->getAccessToken();
$client->setAccessToken($newToken);
I am trying to get a refresh token for the Google API's, using the PHP SDK. I am authenticating the user with Javascript, retrieving a code, and exchanging it for an access_token server side, but this doesn't grant me an access token. What am I doing wrong? Here is the code I use:
$client = new Google_Client();
$client->setClientId($client_id);
$client->setClientSecret($client_secret);
$client->addScope('https://www.googleapis.com/auth/plus.me');
$client->addScope('https://www.google.com/m8/feeds');
$client->setRedirectUri('postmessage');
$client->setAccessType('offline');
if (isset($_REQUEST['code'])) {
$client->authenticate($_REQUEST['code']);
if ($client->getAccessToken()) {
$_SESSION['access_token'] = $client->getAccessToken();
$token_data = $client->verifyIdToken()->getAttributes();
$result['data']=$token_data;
$result['access_token']=json_decode($_SESSION['access_token']);
}
}
debug($result); //my own function, var_dumps the content of an array
Here is the result of the array:
$result['access_token'] contains:
access_token: TOKEN
created: 1434380576
expires_in: 3594
id_token: IDTOKEN
token_type:"Bearer"
If I am not mistaken the first access token should also contain the refresh token, what am I doing wrong?
First check the settings in the developer console of Google to see if your RedirectUri is the same and that the API is activated (although if you already got that .json, then I assume it is.
You have to go through the Google Auth Prompt Screen at least 1 time to get a refresh token in your .json, and if your RedirectUri is taking you nowhere, you won't be able to get your refresh token or even the access validated.
You can also try a service account if you're doing small file transactions and don't need a user validation for the process of your script. Good Luck.
The problem was that I had to specify that I want offline access in the authentication process, the client side... The Google API's are horribly documented!!!
Our application is widely-integrated with Google services like Mail, Contacts, Calendar etc. And for all of these Google services we need access to the user's data permanently, without asking the user for permission every time we need to pull our app with data from Google. It's very important to have instant access to the user's data when we sync contacts, calendar or access mail via Google IMAP.
We also provide access to our app from any Google service via Single-Sign-On (SSO). And here we have a trouble.
We are asking users who install our application for "offline" access to their data. So we can have access to their data instantly, when we run our sync scripts etc.
First time user grants access to our app (via SSO, when it clicks on our app icon), it's asked to grant access to all data API we need - contacts, email, calendar etc. Everything goes okay and user gets logged in our app. But when user accesses our app next times (same via SSO), it gets asked to grant "Have offline access". It's very strange, because first time user doesn't get asked for this. And it also doesn't look nice to ask user every time for "offline access". Why we consider such behaviour strange is because once user has already granted access. So why it gets asked again and again?
The code we are using for SSO is as follows:
$clientId = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.apps.googleusercontent.com";
$clientSecret = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
$callback = "https://our_domain/callback";
$scopes = array(
"https://www.googleapis.com/auth/userinfo.email",
"https://www.googleapis.com/auth/userinfo.profile",
"https://www.googleapis.com/auth/drive.readonly.metadata",
"https://www.googleapis.com/auth/calendar",
"https://www.google.com/m8/feeds",
"https://www.googleapis.com/auth/tasks",
"https://www.googleapis.com/auth/admin.directory.user.readonly",
"https://mail.google.com/"
);
// $params is a list of GET and POST parameters
if (empty($params['code']))
{
$_SESSION['GOOGLE_SSO_STATE'] = md5(uniqid(rand(), true));
$client = new Google_Client();
$client->setClientId($clientId);
$client->setRedirectUri($callback);
$client->setAccessType('offline');
$client->setApprovalPrompt('force');
$client->setState($_SESSION['GOOGLE_SSO_STATE']);
$client->setScopes($scopes);
$url = $client->createAuthUrl();
echo "<script> top.location.href='" . $url . "'</script>";
exit;
}
if (!empty($params['code']) && !empty($params['state']) && $params['state'] == $_SESSION['GOOGLE_SSO_STATE'])
{
unset($_SESSION['GOOGLE_SSO_STATE']);
$client = new Google_Client();
$client->setClientId($clientId);
$client->setClientSecret($clientSecret);
$client->setRedirectUri($callback);
$credentials = $client->authenticate();
// we need refresh token so we can exchange it for new access token when the current ones expires
if (!isset($credentials_['refresh_token']))
{
echo "Wrong credentials received";
exit;
}
$client = new Google_Client();
$client->setUseObjects(true);
$client->setAccessToken($credentials);
$userInfoService = new Google_Oauth2Service($client);
$userInfo = $userInfoService->userinfo->get();
echo $userInfo->getId();
}
Can anyone help us understand this behaviour? Or maybe someone even knows how to make it not asking user for "offline access" every time it accesses the app?
This because your approval prompt is set to "force:
$client->setApprovalPrompt('force');
Use force only if you need to re-issue a refresh token. There is a strange side effect that ask the user time and again for offline access.
We use Service Accounts which provide us with offline access for any user in the installed domain (for the chosen Organiztaion Unit). Depending on what you are trying to do, this may be an option for you.
https://developers.google.com/drive/web/service-accounts