I am working with this package Analytics-Laravel 4 for google analytics and I have follower all of the steps correctly. When I try to get the site id for example, I face this error:
Error refreshing the OAuth2 token, message: '{ "error" : "invalid_grant" }'
I have double checked all of the configurations, client id, service_account and private key but the error still occurs.
Anything else I should try to check that maybe would solve this issue?!
I didn't use this package before, I'm using google-api-php-client, but anyways, this error occurs if you don't set the refresh token.
You should know that you need to have the access token ONLY once. You also need to set the access type to be offline, which will provide you with a refresh token that you will use to automatically get a new access token without getting a new code every time your access token expires.
In google's console, I created a Client ID for web application. Make sure you set the redirect URI to your web page where you will receive the code and will extract the access token using that code.
Here is a code example using google-api-php-client, I hope it will help:
You need to run the following code only once, and retrieve and store the access token.
<?php
require_once('google-api-php-client-master/src/Google/Client.php');
session_start();
$client = new Google_Client();
$client->setApplicationName('APP_NAME');
$client->setClientId(YOUR_CLIENT_ID);
$client->setClientSecret('YOUR_CLIENT_SECRET');
$client->setRedirectUri('YOUR_REDIRECT_URI');
$client->setDeveloperKey('YOUR_DEV_KEY');
$client->setScopes(array('https://www.googleapis.com/auth/analytics.readonly'));
$client->setAccessType("offline");
// Step 1: Create an auth url
if (isset($_GET['ref']) && $_GET['ref'] == "1") {
$authUrl = $client->createAuthUrl();
return Redirect::to($authUrl);
}
// Step 2: The user accepted your access now you need to exchange it.
if (isset($_GET['code'])) {
$client->authenticate($_SESSION['code']); //Authenticate the client
$token = $client->getAccessToken(); //Get the access token
var_dump($token); //Store the token in your DB or config file
die();
}
?>
After getting your access token from the code above (which should contain a refresh token), store it in your DB or a config file.
Now the following code should authenticate the client and refresh the access token when it expires via the getAccessToken function
<?php
require_once('google-api-php-client-master/src/Google/Client.php');
require_once('google-api-php-client-master/src/Google/Service/Analytics.php');
$client = new Google_Client();
$client->setApplicationName('APP_NAME');
$client->setClientId(YOUR_CLIENT_ID);
$client->setClientSecret('YOUR_CLIENT_SECRET');
$client->setRedirectUri('YOUR_REDIRECT_URI');
$client->setDeveloperKey('YOUR_DEV_KEY');
$client->setScopes(array('https://www.googleapis.com/auth/analytics.readonly'));
$client->setAccessType("offline"); //Make sure the access type is offline to get a refresh token
$config = CoreConfig::find(1); //Getting the first record from the config table
$client->setAccessToken($config->google_access_token); //Retrieve the access token that you stored and set it to the client object
//Check this the token is expired
if($client->isAccessTokenExpired()) {
$token = json_decode($config->google_access_token, true); //Get the token stored, and convert JSON to array
$client->refreshToken($token['refresh_token']); //Set the refresh token
$newtoken = $client->getAccessToken(); //Call the getAccessToken() function to get a new access token for you
$config->update(array('google_access_token' => $newtoken)); //Store the new token in your DB
}
if ($client->getAccessToken()) {
$analytics = new Google_Service_Analytics($client);
//Do something with the $analytics object
}
?>
It could be the server time. If the local time on your server is out of sync with google's oAuth server even by a few seconds you'll get that error message.
You can check the time by running "date" in the console.
Running "sudo ntpdate ntp.ubuntu.com" solved it for us.
Related
As detailed in my question to the the Google API team, I would like to work out a way to avoid redirects.
In theory this should be possible as an Authentication Code from one client (JavaScript) should be agnostic of the client and thus it should work if passed to the PHP client to fetch the access and refresh tokens.
Steps in theory:
Client gets an authorization code
Client exchanges the authorization code for the access and refresh tokens
How am I attempting this?
Run the JavaScript client to get the Authentication token
GoogleAuth = gapi.auth2.getAuthInstance()
GoogleAuth.grantOfflineAccess({
scope: 'https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/plus.business.manage https://www.googleapis.com/auth/plus.me openid email profile'
}).then(function (resp) {
var auth_code = resp.code;
console.log("AuthCode:" + auth_code)
})
At this point i get a code, not sure if this is an authorization code or access token but i cannot see any other function in the Javascript library that is Authorization token explicit/specific.
Use the Authentication Token in the PHP API Library
$error = $request->get('error');
$code = $request->get('code');
if($error){
throw new Exception('Error from authenticating ' . $error);
}
$client = new \Google_Client();
$client->setAuthConfig(getcwd() . '/../client_secret.apps.googleusercontent.com.json');
$client->setAccessType("offline"); // offline access
$client->setIncludeGrantedScopes(true); // incremental auth
$client->addScope(
array(
'https://www.googleapis.com/auth/userinfo.email',
'https://www.googleapis.com/auth/userinfo.profile',
'https://www.googleapis.com/auth/plus.business.manage'
)
);
$client->setRedirectUri('http://myserver.com/code');
$client->setApprovalPrompt('force');
$client->fetchAccessTokenWithAuthCode($code);
$accessToken = $client->getAccessToken();
return new Response(
"<html><body>Authenticated with code : " . $code . "<br/>\n\n".
" Access Token is : ". var_export($accessToken, true) . "</body></html>"
);
Access token is still null. the line $accessToken = $client->getAccessToken(); returns null or false.
This works in the full PHP based version but the PHP version is based on creating a link that the user needs to follow, the user is then on the Google server and can approve the app, when approved the user is redirected back to the app. Then the app receives the Authentication code.
I just would like to avoid redirects due to the architecture of single pages apps or just preference. The only alternative I can think of is to open a popup and notify the original window when access and refresh codes are returned so that the PHP client can go on querying the API, but it is an ugly solution IMHO.
Is there another way to get an authorization code that works on PHP but obtained from JavaScript?
I am trying to integrate several Google API calls into a custom Drupal 8 module.
I am basically trying to first get my custom class to get an access token from Google via OAuth before I try do anything else. I am doing this by using a class function with everything simply in one place. The function is as follows:
public function testTokenRequest(): void
{
// Setup Google Client Config within context of initialized class
$this->googleClient->setClientId($this->googleClientID);
$this->googleClient->setClientSecret($this->googleClientSecret);
$this->googleClient->setDeveloperKey($this->googleApiKey);
// Add Google MyBusiness scope
$this->googleClient->setScopes(array('https://www.googleapis.com/auth/plus.business.manage'));
try {
$accessToken = $this->googleClient->getAccessToken(); // null returned where breakpoint hit
$this->googleAccessToken = $accessToken; // Put xdebug breakpoint here
} catch (Exception $exception) {
echo $exception->getMessage();
}
}
Currently all I get is a null returned for the $accessToken = $this->googleClient->getAccessToken(); call.
Unsure where I am going wrong, possibly the AddScopes call because the vendor documentation for the apiclient does this slightly differently, i.e. $client->addScope(Google_Service_Plus::PLUS_ME); but I couldn't find the correct class to use for the MyBusinessAPI scope so used the OAuth playground string instead https://www.googleapis.com/auth/plus.business.manage
I get an OAuth Playground AccessToken returned when I use that but end up with a Permission Denied error instead even though I have the GMB API added to my whitelist under credentials.
Google MyBusiness is Oauth2 based. The access token is not received until the user has approved your app, it is normal that the function returns null if the user has not approved the app yet.
Here is an example on how you create a link where to send the user to start authentication and authorization for your app.
$client = new \Google_Client();
$client->setAuthConfig(getcwd() . '/../client_secret.apps.googleusercontent.com.json');
$client->setAccessType("offline"); // offline access
$client->setIncludeGrantedScopes(true); // incremental auth
$client->addScope(
array(
'https://www.googleapis.com/auth/userinfo.email',
'https://www.googleapis.com/auth/userinfo.profile',
'https://www.googleapis.com/auth/plus.business.manage'
)
);
$client->setRedirectUri('http://server.com/code');
$client->setApprovalPrompt('force');
return new Response(
'<html><body>Authenticate here : <a href="' .
$auth_url = filter_var($client->createAuthUrl(), FILTER_SANITIZE_URL)
. '">HERE</a></body></html>'
);
The example above assumes your server will also implement a /code endpoint where the user is redirected to with the authorization token, then you need to call the api to exchange the token with the access and refresh code.
This document will help you understand further
https://developers.google.com/api-client-library/php/auth/web-app
Is there a way to re-view the accessToken itself after it's been created?
Looking at the code below you can see that $token holds the accessToken for the "Test Token" client and that's fine it works as expected, however, say the user forgot that $token is there a way to display it for the user again?
// user can manually create personal access token
// by using the following
$user = Auth::user();
$token = $user->createToken('Test Token')->accessToken;
// this works fine, however, I want to allow the user to edit / re-view these personal access
// tokens when he/she wants
// I'm able to delete or revoke these tokens but how can I vew the access token again?
// I tried the following:
foreach (Auth::user()->tokens as $token)
{
// but none of these give back that access token??
// halp!
// print "accessToken: " . $token->accessToken;
// print "token: " . $token->token;
}
If I understood correctly: you can store the tokens in DB. If there is more tokens per user you can store it in json format.
The initial task was as simple as this: get the latest post from a Google+ page.
It took 3 days now to find out that all examples on the Internet seem to be either outdated or wrong or not valid. Google developer docs also don't provide much help, complicating things more and more with every new confusing documentation page. So, guys, I'm giving up.
First I tried to implement OAuth 2.0 procedure which was documented in their docs (https://developers.google.com/identity/protocols/OAuth2WebServer). As its title implies it is exactly about connecting from a server app. I followed it, and at first glance, it worked: I got the back call, successfully authenticated, fetched access token and stored it and made a simple call to fetch the posts.
// Initialization
$this->googleClient = new Google_Client();
$this->googleClient->setAuthConfig(Json::decode($config->get('client_json')));
$this->googleClient->setAccessType('offline');
$this->googleClient->setIncludeGrantedScopes(TRUE);
$this->googleClient->addScope('https://www.googleapis.com/auth/plus.me');
$this->googleClient->setRedirectUri(Url::fromRoute('mymodule.gplus.callback')->setAbsolute()->toString());
// The callback
$client->authenticate($code);
$accessToken = $client->getAccessToken();
(The only thing which seemed silly here - is the scope. I had no idea what scope should I claim for if I need to just read a public post from a public page, so I just picked the first random entry which looked related.)
As I said I got the token and could fetch my posts:
// Using Google_Service_Plus
$this->client()->setAccessToken($access_token);
$this->googleServicePlus = new Google_Service_Plus($this->client($reset));
$this->googleServicePlus->activities->listActivities($endpoint, 'public', ['maxResults' => 1]);
But after 1 hour it just stopped working claiming that the token is outdated or something and it needs to be refreshed. And here comes the showstopper: I found no way to refresh the token. $response from authenticate() doesn't return refresh token anymore (although it's been mentioned many times in other answers) so I don't even have a way to refresh it.
I tried digging in the library (from my composer.json: "google/apiclient": "^2.0") and figured out that authenticate() method is actually deprecated there are few other methods which seem to play with tokens. I tried \Google_Client::fetchAccessTokenWithAssertion() which asked for some Application Default Credentials... which leads us to completely different topic and way of authentication described here: https://developers.google.com/identity/protocols/OAuth2ServiceAccount
So should I abandon everything which I did and now implement something new? How could I just do this simple task of fetching news?
Sorry for the long question.
The process you are following is good. The problem you are having is refreshing the token. Although the official documentation states:
If you use a Google API Client Library, the client object refreshes the access token as needed as long as you configure that object for offline access.
It does not explain how to do it using the PHP Client Library. This was a problem for me too so this is the approach I'm taking and hopefully it can help you.
// 1. Build the client object
$client = new Google_Client();
$client->setRedirectUri('http://' . $_SERVER['HTTP_HOST'] . '/index.php');
$client->setAuthConfig("client_secret.json");
$client->addScope($scopes);
$client->setAccessType("offline");
I normally save the Access Token to the session, therefore before proceeding, I check if the access token is already saved to the session. If it is, then I proceed to check if the access token is already expired. If it is, then I proceed to refresh the access token, and then I proceed to make the API Call.
// 2. Check if the access token is already saved to session
if( isset($_SESSION["access_token"]) && ($_SESSION["access_token"]) ) {
//set access token before checking if already expired
$client->setAccessToken($_SESSION["access_token"]);
//check if access token is already expired and refresh if so
if ($client->isAccessTokenExpired()) {
$refreshToken = $_COOKIE["refresh_token"]; //get refresh token
$client->refreshToken($refreshToken); // refresh the access token
}
//get new access token and save it to session
$_SESSION['access_token'] = $client->getAccessToken();
// set access token after checking if already expired
$client->setAccessToken($_SESSION["access_token"]);
$plusService = new Google_Service_Plus($client);
$optParams = array(
"maxResults" => 5,
"pageToken" => null
);
$activitiesList = $plusService->activities->listActivities("+cnn", "public", $optParams);
$activities = $activitiesList->getItems();
foreach ($activities as $activity ) {
print_r($activity);
print "<br>**********************<br>";
}
}
If the access token is not saved to the session, this means that the authentication and authorization has not taken place, so I proceed to authenticate the user.
// 3. Authenticate user since access token is not saved to session
else {
if( !isset($_GET["code"]) ){ //get authorization code
$authUrl = $client->createAuthUrl();
header('Location: ' . filter_var($authUrl, FILTER_SANITIZE_URL));
} else { //exchange authorization code for access token
$client->authenticate($_GET['code']); //authenticate client
//get access token and save it to session
$_SESSION['access_token'] = $client->getAccessToken();
//save refresh token to a Cookie
$refreshToken = $_SESSION["access_token"]["refresh_token"];
setcookie("refresh_token", $refreshToken, time() + (86400 * 30), "/");
$redirect_uri = 'http://' . $_SERVER['HTTP_HOST'] . '/index.php';
header('Location: ' . filter_var($redirect_uri, FILTER_SANITIZE_URL));
}
}
Please note: For demonstration purposes, I'm saving the refresh token to a cookie in this example; However, it is well known that you should not save this info to a cookie but instead to a secure database. Also, the authenticate() method is not deprecated, it's just an alias for the method fetchAccessTokenWithAuthCode(). Another thing, the scope you are using is not silly, since you are fetching info from a public page, according to the documentation here and here, I intuited that I should only allow access to Know who you are on Google https://www.googleapis.com/auth/plus.me.
After successful authentication via a form post sign-in, I need to be able to use the same session token within the response to do another post to a protected route, but this time using XMLHttpRequest.
How would I get the session token, considering that the successful authentication response has already passed.
The session token is stored in a laravel_session cookie, assuming default Laravel settings (see config/session.php).
You can read the cookie in javascript using document.cookie. For example:
function readCookie(name)
{
var matches = document.cookie.match('(^|; )'+name+'=([^;]*)');
if (matches) {
return decodeURIComponent(matches[2]);
}
return null;
}
var token = readCookie('laravel_session');