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
Related
First of all, i am an absolute beginner in google apis. I followed this tutorial along with many other tutorials on the web -> https://www.webslesson.info/2019/09/how-to-make-login-with-google-account-using-php.html
and it is successfully working. But i cant refresh the page after login because i dont have a refresh token.
My config.php-
<?php
session_start();
require_once 'vendor/autoload.php';
$google_client = new Google_Client();
$google_client->setClientId('client id here');
$google_client->setClientSecret('client screct here');
$google_client->setRedirectUri('http://localhost/realestate/index.php');
$google_client->addScope('email');
$google_client->addScope('profile');
$google_client->setAccessType('offline');
$google_client->setApprovalPrompt('force');
?>
My index file-
<?php
//Include Configuration File
include('config.php');
$login_button = '';
if(isset($_GET["code"]))
{
$token = $google_client->fetchAccessTokenWithAuthCode($_GET["code"]);
if(!isset($token['error']))
{
$google_client->setAccessToken($token['access_token']);
$_SESSION['access_token'] = $token['access_token'];
$google_service = new Google_Service_Oauth2($google_client);
$data = $google_service->userinfo->get();
if(!empty($data['given_name']))
{
$_SESSION['user_first_name'] = $data['given_name'];
}
if(!empty($data['family_name']))
{
$_SESSION['user_last_name'] = $data['family_name'];
}
if(!empty($data['email']))
{
$_SESSION['user_email_address'] = $data['email'];
}
if(!empty($data['gender']))
{
$_SESSION['user_gender'] = $data['gender'];
}
if(!empty($data['picture']))
{
$_SESSION['user_image'] = $data['picture'];
}
}
}
if(!isset($_SESSION['access_token']))
{
$login_button = 'Login With Google';
}
?>
I asked a question somewhat related to this question but that was not working. So I asked again here, sorry for that.
So,how can i setup refresh token ?
You actually do have a refresh token. THe issue or rather google thinks you do.
When you add this to your code
$google_client->setAccessType('offline');
It tells google that you would like to access the users data when the user is not online.
However the problem you are having is that with some languages the PHP for example, Google sends the refresh token to you the first time the user authenticates and then they assume that you have stored it on your server someplace so that you can use it the next time. If you log the user in again you often will not be granted a new refresh token because again they think you have one.
How to fix this is to have the user revoke your access revoke. Then the next time they visit your site they should be prompted to login this time you should have a refresh token. Remember to store it someplace so that you have it next time.
Also when you refresh the access token remember to check that the refresh token is there and if its different then the one you have then store the new one
// Refresh the token if it's expired.
if ($client->isAccessTokenExpired()) {
$client->fetchAccessTokenWithRefreshToken($client->getRefreshToken());
file_put_contents($credentialsPath, json_encode($client->getAccessToken()));
}
Update for the frustrated. user447951
If you are not seeing a refresh token even after adding offline it is because this client library does not return to you a refresh token after every call, authorization request or refresh of the access token. (yes some of the google client libraries do, this one does not)
It doesn't because It is assumed when the user authorized the application the first time that the developer stored it, so the next time you authorize the user you will not get a new refresh token back. Nor will you get one when you refresh the access token.
You need to go to either the users google account and revoke access for the third party app then request authorizing of the user again. or use the revoke endpoint to revoke the users access. When the user is prompted again to request authorizing then a new refresh token will be returned. This time save it.
NO it is not enough to just remove the credentials that you have stored in your system and use force prompt. There must be nothing in the users account to state they had previously granted this application access.
use the revoke end point to revoke the users access to the app
Go to the users account under third party pass with access and revoke it.
I am building a portal where multiple users can log in to their multiple Gmail accounts. I have successfully retrieved the token value, However, I want to store that in my database but I am unable to store it.
Below is the code I am using:
function mInititalize(){
$client = new Google_Client();
$client->addScope('https://www.googleapis.com/auth/plus.login https://www.googleapis.com/auth/userinfo.email https://mail.google.com/');
$client->setClientId(Config('gmail.client_id'));
$client->setClientSecret(Config('gmail.client_secret'));
$client->setRedirectUri('http://localhost:81'.Config('gmail.redirect_url'));
$loginURL = $client->createAuthUrl();
return redirect($loginURL);
}
After Redirection or user login
function mGetToken(){
$token = $client->fetchAccessTokenWithAuthCode( 'code'); // here i get the 'code' from login URL
I pass this code to get token I successfully get token
$oAuth = new Google_Service_Oauth2( $client);
$userData = $oAuth->userinfo_v2_me->get(); // get current user detail
}
I want to store $token value in database, but I am getting error message
>Serialization of 'Closure' is not allowed
Please anyone help me to solve this issue. Thanks.
I would suggest storing OAuth credential information for the Google API, not in your database, but through the API itself. If you're intending to use it any authentication manner, you'll run into problems, as the docs state:
Access tokens periodically expire and become invalid credentials for a related API request. Google Identity Platform: Using OAuth 2.0 for Web Server Applications
But, the same docs also show a way that you can set or retrieve the token natively within the API. Since it's data relating to google's auth'ing process, and since it might go stale if you store it, it seems best to just let them handle it and work with the API. The same source:
If you need to apply an access token to a new Google_Client object—for example, if you stored the access token in a user session—use the setAccessToken method:
$client->setAccessToken($access_token);
$client->getAccessToken();
I previously had issue with the Google account to access Youtube's API. So i created a new fresh gmail account & managed to get it working. Not only after like One hour. I found out that Refresh Token wasn't refreshing. I don't know why. This is becoming frustrating as it seems i would have to forfeit using Youtube services any longer.
This is my code below: perhaps i'm doing something wrong.
$client = new Google_Client();
$client->setClientId($OAUTH2_CLIENT_ID);
$client->setClientSecret($OAUTH2_CLIENT_SECRET);
$client->setScopes('https://www.googleapis.com/auth/youtube');
$redirect = filter_var('http://' . $_SERVER['HTTP_HOST'] . $_SERVER['PHP_SELF'],
FILTER_SANITIZE_URL);
$client->setRedirectUri($redirect);
$client->setAccessType('offline');
$client->setApprovalPrompt('force'); // this line is important when you revoke permission from your app, it will prompt google approval dialogue box forcefully to user to grant offline access
// Define an object that will be used to make all API requests.
$youtube = new Google_Service_YouTube($client);
// Check if an auth token exists for the required scopes
$tokenSessionKey = 'token-' . $client->prepareScopes();
$_SESSION[$tokenSessionKey] = $manually_stored_token; //$client->getAccessToken();
if (isset($_SESSION[$tokenSessionKey])) {
$client->setAccessToken($_SESSION[$tokenSessionKey]);
}
// Check to ensure that the access token was successfully acquired.
if($client->isAccessTokenExpired()) {
$client->refreshToken($refresh_token);
if(!$client->isAccessTokenExpired()) {
// do something
} else {
// refresh access token not granted
}
} else if ($client->getAccessToken()) {
// do something
} else {
// do something
}
I checked my account https://myaccount.google.com/u/0/security, under "Sign-in & Security" for authorized access to your Google Account, I still have authorization. So i don't know why its not refreshing my Token.
When you first authorise, you are given both the access_token and refresh_token in the response.
After that, you will only get a access_token back.
The refresh_token you must save and re-use.
If for what ever reason you loose it, you must reset your authorise access.
I had this issue and had to make a dirty hack as no one could give me an answer.
Basic fix is to "un-authorise" a user and then "re-authorise" them again. As if you do not, you will never get the refresh_token back.
To test and inspect. Open your account that is being authorised.
Go into the Dashboard and find "Apps connected to your account" section.
There you will see your app and can manually remove it for testing.
However, you will need to code for this later, but is good enough for testing.
I am using youtube API for scheduling live events for user in my application.
Once the user logged in my application i need to logged in the same user to our business google account(One google account for all user) without giving login credentials. and to get access token for scheduling the live events.
Is it possible to login the user into google account without giving login credentials(User will not feel he is login to another account).
Is it feasible with PHP?.Please give one example to get access token for youtube API access.
I used the following code for getting access token but service account can't access the youtube service.
My code for getting access token using service account:
<?php
require_once 'Google/autoload.php';
session_start();
$client_id = '395540674667-p64tdfqdsfsdfdsf#dsfd.com';
$Email_address = '54564-drgfdg1#developer.gserviceaccount.com';
$key_file_location = 'Youtube API-5czxczxc86.p12';
$client = new Google_Client();
$client->setApplicationName("Youtube API");
$key = file_get_contents($key_file_location);
// seproate additional scopes with a comma
$scopes = array('https://www.googleapis.com/auth/sqlservice.admin','https://www.googleapis.com/auth/plus.login','https://www.googleapis.com/auth/youtube');
$cred = new Google_Auth_AssertionCredentials($Email_address,
$scopes,
$key);
$client->setAssertionCredentials($cred);
if($client->getAuth()->isAccessTokenExpired()) {
$client->getAuth()->refreshTokenWithAssertion($cred);
}
//print_r($client);
echo $client->getAccessToken();
?>
I am expecting the answer is something like this.Any one please help
you example about Youtube api, first:
select credentials to your api project from here:
https://console.cloud.google.com/home/dashboard?project= with your project
also you need to have the api enable because google told us "Some APIs are enabled automatically."
when you select:
https://console.cloud.google.com/apis/api/youtube/ with Go to Credentials you have some option into modal window this will help
also.
the php code seem to be ok but also I think you will change after you learn more about Credentials settings.
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!!!