Google Sheet Access Token - php

I am a bit confused on the Access token. I have written a PHP script which inserts the data I get from the POST request , I already have authorized the App and it does add the Row at the end of the Sheet.
My Question is how I refresh the token when it get implemented on the server, as it will add the POST data.
Here is the Code
<?php
// Load the Google API PHP Client Library.
require_once __DIR__ . '/vendor/autoload.php';
session_start();
$client = new Google_Client();
$client->setAuthConfigFile(__DIR__ . '/client_secrets.json');
$client->addScope(Google_Service_Sheets::SPREADSHEETS);
if (isset($_SESSION['access_token']) && $_SESSION['access_token']) {
$client->setAccessToken($_SESSION['access_token']);
$lead = array (
"first_name" => $_POST['name'],
"email" => $_POST['email']
);
$sid = "sheet id on which the row is added";
addRowToSpreadsheet($lead, $client , $sid);
} else {
$redirect_uri = 'http://' . $_SERVER['HTTP_HOST'] . '/fb/oauth2callback.php';
header('Location: ' . filter_var($redirect_uri, FILTER_SANITIZE_URL));
}
function addRowToSpreadsheet($ary_values = array(), $client , $sid) {
$sheet_service = new Google_Service_Sheets($client);
$fileId = $sid;
$values = array();
foreach( $ary_values AS $d ) {
$cellData = new Google_Service_Sheets_CellData();
$value = new Google_Service_Sheets_ExtendedValue();
$value->setStringValue($d);
$cellData->setUserEnteredValue($value);
$values[] = $cellData;
}
// Build the RowData
$rowData = new Google_Service_Sheets_RowData();
$rowData->setValues($values);
// Prepare the request
$append_request = new Google_Service_Sheets_AppendCellsRequest();
$append_request->setSheetId(0);
$append_request->setRows($rowData);
$append_request->setFields('userEnteredValue');
// Set the request
$request = new Google_Service_Sheets_Request();
$request->setAppendCells($append_request);
// Add the request to the requests array
$requests = array();
$requests[] = $request;
// Prepare the update
$batchUpdateRequest = new Google_Service_Sheets_BatchUpdateSpreadsheetRequest(array(
'requests' => $requests
));
try {
// Execute the request
$response = $sheet_service->spreadsheets->batchUpdate($fileId, $batchUpdateRequest);
if( $response->valid() ) {
// Success, the row has been added
return true;
}
} catch (Exception $e) {
// Something went wrong
error_log($e->getMessage());
}
return false;
}
?>
I have tried hosting the app on the server and it doesn't add a new row in the Sheet, I think this is a problem due to the Access Token
Please Help

There is actually a PHP Quickstart for Sheets API which includes how to refresh tokens. Here's a snippet:
// Refresh the token if it's expired.
if ($client->isAccessTokenExpired()) {
$client->fetchAccessTokenWithRefreshToken($client->getRefreshToken());
file_put_contents($credentialsPath, json_encode($client->getAccessToken()));
}
There's also a Refreshing an access token (offline access) guide with regard to refresh tokens
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.
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. If you are not using a client library, you need to set
the access_type HTTP query parameter to offline when redirecting the
user to Google's OAuth 2.0 server. In that case, Google's
authorization server returns a refresh token when you exchange an
authorization code for an access token. Then, if the access token
expires (or at any other time), you can use a refresh token to obtain
a new access token.
If your application needs offline access to a Google API, set the API
client's access type to offline:
$client->setAccessType("offline");

Related

Google Calendar API - PHP

I am currently using the Google Calendar API for a web application. However, every hour, I am prompted with a link to verify quickstart access. Does anyone know how to fix this?
Details:
I have created a new gmail id: redu#gmail.com
redu#gmail.com has an associated calendar
My php based web application needs to do the following with calendar:
Create a new calendar for every registered user (as an additional calendar for redu#gmail.com)
Create an event for a logged in user and add another registered user as an invitee
I have tried using OAUTH and service accounts with no luck. Any help is greatly appreciated.
Below is the code that creates Google_Client and Srvice objects using service account's credentials
function __construct()
{
Service account based client creation.
$this->client = new Google_Client();
$this->client->setApplicationName("Redu");
$this->client->setAuthConfig(CREDENTIALS_PATH);
$this->client->setScopes([SCOPES]);
$this->client->setSubject('redu#gmail.com');
$this->client->setAccessType('offline');
$this->service = new Google_Service_Calendar($this->client);
}
When I try to use the $service object to create a calendar or create an event I get an error saying that domain wide permissions are not setup. However, when I created the service account I did enable domain wide delegation.
EDIT:
Below is my code to create a Google_Client using service account key and use the client to create a new calendar for redu#gmail.com. Note that I shared redu#gmail.com's calendar with reduservice#subtle-breaker-280602.iam.gserviceaccount.com and set the permission to "Manage Changes and Manage Sharing". The error I am getting is below the code:
require (__DIR__.'/../../../vendor/autoload.php');
define('CREDENTIALS_PATH', __DIR__ . '/redu_service_account_credentials.json');
define('SCOPES', Google_Service_Calendar::CALENDAR);
function createNewCalendar($userName) {
//Service account based client creation.
$client = new Google_Client();
$client->setApplicationName("REdu");
// path to the credentials file obtained upon creating key for service account
$client->setAuthConfig(CREDENTIALS_PATH);
$client->setScopes([SCOPES]);
$client->setSubject('redu#gmail.com');
$client->setAccessType('offline');
$service = new Google_Service_Calendar($client);
$calendar = new Google_Service_Calendar_Calendar();
$calendar->setSummary($userName);
$calendar->setTimeZone('America/Los_Angeles');
$createdCalendar = $service->calendars->insert($calendar);
// Make the newly created calendar public
$rule = new Google_Service_Calendar_AclRule();
$scope = new Google_Service_Calendar_AclRuleScope();
$scope->setType("default");
$scope->setValue("");
$rule->setScope($scope);
$rule->setRole("reader");
// Make the calendar public
$createdRule = $service->acl->insert($createdCalendar->getId(), $rule);
return $createdCalendar->getId();
}
ERROR:
Fatal error: Uncaught exception 'Google_Service_Exception' with message '{
"error": "unauthorized_client",
"error_description": "Client is unauthorized to retrieve access tokens using this method, or client not authorized for any of the scopes requested."
}'
OAUTH2 vs Service accounts
Oauth2 and service accounts are two different things. You use oauth2 if you are trying to access a users data. The consent window you mentioned will prop up and ask that they grant permission for your application to access their data.
Service accounts on the other hand are dummy users who can be pre approved to access data you the developer control. You could share a calendar with a service account granting it access to that calendar it will no need to be authenticated in the same manner as a user.
A service account will never popup and request access again.
Oauth2 example with refresh token.
The issue is that your access token is expiring. If it expires then the user will need to grant your application access to their data again. To avoid this we use a refresh token and store that in a session varable and when the acces stoken expires we just request a new one.
Notice how i am requesting $client->setAccessType("offline"); this will give me a refresh token.
the session vars are now set storing this data
$_SESSION['access_token'] = $client->getAccessToken();
$_SESSION['refresh_token'] = $client->getRefreshToken();
Then latter i can check if the access token is expired if so i refresh it
if ($client->isAccessTokenExpired()) {
$client->fetchAccessTokenWithRefreshToken($client->getRefreshToken());
$client->setAccessToken($client->getAccessToken());
$_SESSION['access_token'] = $client->getAccessToken();
}
oauth2callback.php
require_once __DIR__ . '/vendor/autoload.php';
require_once __DIR__ . '/Oauth2Authentication.php';
// Start a session to persist credentials.
session_start();
// Handle authorization flow from the server.
if (! isset($_GET['code'])) {
$client = buildClient();
$auth_url = $client->createAuthUrl();
header('Location: ' . filter_var($auth_url, FILTER_SANITIZE_URL));
} else {
$client = buildClient();
$client->authenticate($_GET['code']); // Exchange the authencation code for a refresh token and access token.
// Add access token and refresh token to seession.
$_SESSION['access_token'] = $client->getAccessToken();
$_SESSION['refresh_token'] = $client->getRefreshToken();
//Redirect back to main script
$redirect_uri = str_replace("oauth2callback.php",$_SESSION['mainScript'],$client->getRedirectUri());
header('Location: ' . filter_var($redirect_uri, FILTER_SANITIZE_URL));
}
Authentication.php
require_once __DIR__ . '/vendor/autoload.php';
/**
* Gets the Google client refreshing auth if needed.
* Documentation: https://developers.google.com/identity/protocols/OAuth2
* Initializes a client object.
* #return A google client object.
*/
function getGoogleClient() {
$client = getOauth2Client();
// Refresh the token if it's expired.
if ($client->isAccessTokenExpired()) {
$client->fetchAccessTokenWithRefreshToken($client->getRefreshToken());
file_put_contents($credentialsPath, json_encode($client->getAccessToken()));
}
return $client;
}
/**
* Builds the Google client object.
* Documentation: https://developers.google.com/identity/protocols/OAuth2
* Scopes will need to be changed depending upon the API's being accessed.
* Example: array(Google_Service_Analytics::ANALYTICS_READONLY, Google_Service_Analytics::ANALYTICS)
* List of Google Scopes: https://developers.google.com/identity/protocols/googlescopes
* #return A google client object.
*/
function buildClient(){
$client = new Google_Client();
$client->setAccessType("offline"); // offline access. Will result in a refresh token
$client->setIncludeGrantedScopes(true); // incremental auth
$client->setAuthConfig(__DIR__ . '/client_secrets.json');
$client->addScope([YOUR SCOPES HERE]);
$client->setRedirectUri(getRedirectUri());
return $client;
}
/**
* Builds the redirect uri.
* Documentation: https://developers.google.com/api-client-library/python/auth/installed-app#choosingredirecturi
* Hostname and current server path are needed to redirect to oauth2callback.php
* #return A redirect uri.
*/
function getRedirectUri(){
//Building Redirect URI
$url = $_SERVER['REQUEST_URI']; //returns the current URL
if(strrpos($url, '?') > 0)
$url = substr($url, 0, strrpos($url, '?') ); // Removing any parameters.
$folder = substr($url, 0, strrpos($url, '/') ); // Removeing current file.
return (isset($_SERVER['HTTPS']) ? "https" : "http") . '://' . $_SERVER['HTTP_HOST'] . $folder. '/oauth2callback.php';
}
/**
* Authenticating to Google using Oauth2
* Documentation: https://developers.google.com/identity/protocols/OAuth2
* Returns a Google client with refresh token and access tokens set.
* If not authencated then we will redirect to request authencation.
* #return A google client object.
*/
function getOauth2Client() {
try {
$client = buildClient();
// Set the refresh token on the client.
if (isset($_SESSION['refresh_token']) && $_SESSION['refresh_token']) {
$client->refreshToken($_SESSION['refresh_token']);
}
// If the user has already authorized this app then get an access token
// else redirect to ask the user to authorize access to Google Analytics.
if (isset($_SESSION['access_token']) && $_SESSION['access_token']) {
// Set the access token on the client.
$client->setAccessToken($_SESSION['access_token']);
// Refresh the access token if it's expired.
if ($client->isAccessTokenExpired()) {
$client->fetchAccessTokenWithRefreshToken($client->getRefreshToken());
$client->setAccessToken($client->getAccessToken());
$_SESSION['access_token'] = $client->getAccessToken();
}
return $client;
} else {
// We do not have access request access.
header('Location: ' . filter_var( $client->getRedirectUri(), FILTER_SANITIZE_URL));
}
} catch (Exception $e) {
print "An error occurred: " . $e->getMessage();
}
}
?>
code for service account
The credential files are different dont mix them up.
function getServiceAccountClient() {
try {
// Create and configure a new client object.
$client = new Google_Client();
$client->useApplicationDefaultCredentials();
$client->addScope([YOUR SCOPES HERE]);
return $client;
} catch (Exception $e) {
print "An error occurred: " . $e->getMessage();
}
}
Error
Client is unauthorized to retrieve access tokens using this method, or client not authorized for any of the scopes requested.
There are two types of clients Oauth2 clients and Service account clients. The .json file you download is diffrent for each client. As is the code you will use for each client. You cant interchange this code.
The error you are getting stats that the client you are using cant be used for the code you are using. Try to download the client secret .json for the service account again.,
Here's a working example that generates the authentication object using the Service Account's JSON file
$client = new Google\Client();
$client->setApplicationName(APP_NAME);
$client->setAuthConfig(PATH_TO_JSON_FILE);
$client->setScopes(['YOUR_SCOPE1','YOUR_SCOPE2']);
$client->setSubject(EMAIL_OF_PERSON_YOURE_IMPERSONATING);
$client->setAccessType('offline');
$service = new Google_Service_Drive($client);
// Do stuff with the $service object
Generate Service Account in Google API Console
Delegate domain wide authority to that Service Account's Client ID in Google workspace and define the scopes that the Service Account will have access to
Use the code above and make sure to include one more more relevant scopes

Using Refresh Token on Gmail API. Keeping track of old keys

TL;DR
The problem here is that I cannot do a fetchAccessTokenWithRefreshToken() without first doing setAccessToken() and in order to do setAccessToken() I need to know the previous access token.
If I try to fetchAccessTokenWithRefreshToken() without first calling setAccessToken() I get "LogicException: refresh token must be passed in or set as part of setAccessToken"
If I try to setAccessToken() with only the refresh_token value, it fails as well with Invalid token format
The only way it works is by supplying a FULL valid auth token bundle, not just the refresh token.
I am using this script to get the first auth and generate the refresh token:
<?php
require __DIR__ . '/vendor/autoload.php';
$client = new Google_Client();
$client->setApplicationName('Gmail API Generate Refresh Token');
$client->setScopes(Google_Service_Gmail::GMAIL_MODIFY);
$client->setAuthConfig('credentials.json');
$client->setAccessType('offline');
$client->setPrompt('select_account consent');
// 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);
$client->setAccessToken($accessToken);
if (array_key_exists('error', $accessToken)) {
throw new Exception(join(', ', $accessToken));
}
print "\n\nBelow is the refresh token.\n\n";
print "Use this token with an authenticated Google_Client object to refresh auth tokens.\n";
print "This string MUST be saved otherwise you will need user approval again\n\n";
print $client->getRefreshToken();
file_put_contents('token.json', json_encode($client->getAccessToken()));
print "\n\nThe first auth token bundle has been saved to token.json\n";
Overview
I have a fully-working server-side application that is used to interact with a single user's Gmail inbox.
I have the API creds, and the refresh token saved in persistent secure storage.
The application can succesfully interact with Gmail until the access token expires.
Overall the goal here is to be able to interact with the Gmail API when the application has ONLY the following: valid client id/secret, and a refresh_token. What I'm finding is that I need to keep track of the auth tokens as well as the refresh token.
Here is the issue I'm facing:
Once the access token expires, I cannot generate a new one using only my API creds and the refresh token.
I get an error invalid_grant if I try to call fetchAccessTokenWithRefreshToken() with only the refresh token string as a parameter.
The only way I can get it to give me a new token is to supply the refresh token AND the current access token information!
Not only do I need the original access token itself, but I also need the created and expires_in values.
I have to pass it a full array of info: [access_token, expires_in, created, refresh_token] otherwise it simply won't work!
Clearly I'm doing something wrong here, I would think all I need is the refresh token to generate access tokens as needed.
[PHP] Here are some snippets:
(note these are dev only, I'm not planning to hard-code secrets in the code)
Here is what I would "expect" to work (this does NOT work):
Error here is invalid_grant
$client = new Google_Client();
$client->setApplicationName('Gmail API PHP Quickstart');
$client->setScopes(Google_Service_Gmail::GMAIL_MODIFY);
$client->setAuthConfig('credentials.json');
$client->setAccessType('offline');
$client->setPrompt('select_account consent');
// This is the REFRESH token
$token = '1\/\/0dKcfaketokentokentokenfaketoken-L9IriuoNveLzVQ1w4-lPfakeEPn1R1NjcOK2ISE--O1PO1yEtokenr87E';
// var_dump just for sanity to ensure this returns true
var_dump($client->isAccessTokenExpired());
if ($client->isAccessTokenExpired()) {
var_dump($client->fetchAccessTokenWithRefreshToken($token));
}
THIS WORKS, Because I'm feeding it the full $token array (or is it because I've pre-provided setAccessToken() beforehand. :
$token = array();
$token['access_token'] = '<<SCRUBBED_CURRENT_ACCESS_TOKEN>>>';
$token['expires_in'] = 3599;
$token['refresh_token'] = '<<SCRUBBED_REFRESH_TOKEN>>';
$token['created'] = 1587447211;
// If I leave out ANY of the values above, the token refresh does not work!
// omitted some Gmail client configuration and setup.
$this->client->setAccessToken($token);
if ($this->client->isAccessTokenExpired()) {
$this->accessToken = $this->client->fetchAccessTokenWithRefreshToken($token);
}
else {
$this->accessToken = $this->client->getAccessToken();
}
$this->service = new Google_Service_Gmail($this->client);
Clue:
I noticed on https://github.com/googleapis/google-api-php-client/blob/1fdfe942f9aaf3064e621834a5e3047fccb3a6da/src/Google/Client.php#L275
The fetchAccessTokenWithRefreshToken() will fall back to the existing token, this explains why it'll work when I pre-set an existing token. At least that mystery seems to be solved. Does not explain why it's still not working without a pre-set token.
I would expect it to just work like this:
// Omitted initial Gmail client setup
if ($this->client->isAccessTokenExpired()) {
$this->accessToken = $this->client->fetchAccessTokenWithRefreshToken('<MY_REFRESH_TOKEN');
}
else {
$this->accessToken = $this->client->getAccessToken();
}
$this->service = new Google_Service_Gmail($this->client);
This also doesn't work (confirmed that the getRefreshToken() value is good):
$this->client->fetchAccessTokenWithRefreshToken($this->client->getRefreshToken());
invalid_grant
Means that the token you are using is invaid or expired, or you are trying to use a valid refresh token with a client id and secret that were not used to create it. In this case you are sending an object and should only be sending valid refresh token there for the value you are sending to the method is incorect.
This fetchAccessTokenWithRefreshToken method takes a refresh token not an object. Just pass it the refresh token.
Example: ClientTest.php#L485
client creation
$client = new Google_Client();
$client->setAccessType("offline"); // offline access. Will result in a refresh token
$client->setIncludeGrantedScopes(true); // incremental auth
$client->setAuthConfig(__DIR__ . '/client_secrets.json');
$client->addScope([YOUR SCOPES HERE]);
$client->setRedirectUri(getRedirectUri());
check for expiration and refresh if needed.
if ($client->isAccessTokenExpired()) {
$client->fetchAccessTokenWithRefreshToken($client->getRefreshToken());
}
Note
If you are are getting the refresh token saved and created by another script that the system refreshing the token must use the same client id and client secret IE (client_secrets.json) in order to be able to use it to refresh the access. It cant just be another one in the same project it must be the same secrets.
also see Oauth2Authencation.php
Full example saving token to folder
oauth2callback.php
require_once __DIR__ . '/vendor/autoload.php';
require_once __DIR__ . '/Oauth2Authentication.php';
// Start a session to persist credentials.
session_start();
// Handle authorization flow from the server.
if (! isset($_GET['code'])) {
$client = buildClient();
$auth_url = $client->createAuthUrl();
header('Location: ' . filter_var($auth_url, FILTER_SANITIZE_URL));
} else {
$client = buildClient();
$client->authenticate($_GET['code']); // Exchange the authencation code for a refresh token and access token.
// Add access token and refresh token to seession.
$_SESSION['access_token'] = $client->getAccessToken();
$_SESSION['refresh_token'] = $client->getRefreshToken();
//Redirect back to main script
$redirect_uri = str_replace("oauth2callback.php",$_SESSION['mainScript'],$client->getRedirectUri());
header('Location: ' . filter_var($redirect_uri, FILTER_SANITIZE_URL));
}
Oauth2Authentication.php
require_once __DIR__ . '/vendor/autoload.php';
/**
* Gets the Google client refreshing auth if needed.
* Documentation: https://developers.google.com/identity/protocols/OAuth2
* Initializes a client object.
* #return A google client object.
*/
function getGoogleClient() {
$client = getOauth2Client();
// Refresh the token if it's expired.
if ($client->isAccessTokenExpired()) {
$client->fetchAccessTokenWithRefreshToken($client->getRefreshToken());
file_put_contents($credentialsPath, json_encode($client->getAccessToken()));
}
return $client;
}
/**
* Builds the Google client object.
* Documentation: https://developers.google.com/identity/protocols/OAuth2
* Scopes will need to be changed depending upon the API's being accessed.
* Example: array(Google_Service_Analytics::ANALYTICS_READONLY, Google_Service_Analytics::ANALYTICS)
* List of Google Scopes: https://developers.google.com/identity/protocols/googlescopes
* #return A google client object.
*/
function buildClient(){
$client = new Google_Client();
$client->setAccessType("offline"); // offline access. Will result in a refresh token
$client->setIncludeGrantedScopes(true); // incremental auth
$client->setAuthConfig(__DIR__ . '/client_secrets.json');
$client->addScope([YOUR SCOPES HERE]);
$client->setRedirectUri(getRedirectUri());
return $client;
}
/**
* Builds the redirect uri.
* Documentation: https://developers.google.com/api-client-library/python/auth/installed-app#choosingredirecturi
* Hostname and current server path are needed to redirect to oauth2callback.php
* #return A redirect uri.
*/
function getRedirectUri(){
//Building Redirect URI
$url = $_SERVER['REQUEST_URI']; //returns the current URL
if(strrpos($url, '?') > 0)
$url = substr($url, 0, strrpos($url, '?') ); // Removing any parameters.
$folder = substr($url, 0, strrpos($url, '/') ); // Removeing current file.
return (isset($_SERVER['HTTPS']) ? "https" : "http") . '://' . $_SERVER['HTTP_HOST'] . $folder. '/oauth2callback.php';
}
/**
* Authenticating to Google using Oauth2
* Documentation: https://developers.google.com/identity/protocols/OAuth2
* Returns a Google client with refresh token and access tokens set.
* If not authencated then we will redirect to request authencation.
* #return A google client object.
*/
function getOauth2Client() {
try {
$client = buildClient();
// Set the refresh token on the client.
if (isset($_SESSION['refresh_token']) && $_SESSION['refresh_token']) {
$client->refreshToken($_SESSION['refresh_token']);
}
// If the user has already authorized this app then get an access token
// else redirect to ask the user to authorize access to Google Analytics.
if (isset($_SESSION['access_token']) && $_SESSION['access_token']) {
// Set the access token on the client.
$client->setAccessToken($_SESSION['access_token']);
// Refresh the access token if it's expired.
if ($client->isAccessTokenExpired()) {
$client->fetchAccessTokenWithRefreshToken($client->getRefreshToken());
$client->setAccessToken($client->getAccessToken());
$_SESSION['access_token'] = $client->getAccessToken();
}
return $client;
} else {
// We do not have access request access.
header('Location: ' . filter_var( $client->getRedirectUri(), FILTER_SANITIZE_URL));
}
} catch (Exception $e) {
print "An error occurred: " . $e->getMessage();
}
}
Note: refresh token is only guaranteed to return with the first call the first time the user consents to your accessing their data there google assumes you have stored the refresh token so they dont send a new one. This is why only the access token is saved again after the system automatically refresh the access token using the stored refresh token.

Google Events API returning 401 error unauthorized_client

I have created an application in laravel to sync my users calendar in my database with incremental sync every thing working perfect when i am running it in my browser.
but its returning 401 unauthorized_client in my cron 100% same code implemented in both files.
$client = $this->getGoogleClient();
$client->setAccessToken($accessToken);
if ($client->isAccessTokenExpired()) {
$client->fetchAccessTokenWithRefreshToken($client->getRefreshToken());
}
$gCalService = new Google_Service_Calendar($client);
$optArr = array();
$calendarId = 'primary';
$pageToken = NULL;
$responceEvents = array();
$synced = $emailSync->getGoogleSynced();
if($synced != null)
$optArr['syncToken'] = $synced->sync_token;
do {
if ($pageToken) {
$optArr['pageToken'] = $pageToken;
}
try {
$results = $gCalService->events->listEvents($calendarId,$optArr);
}
catch (Google_Service_Exception $e) {
$msg = $e->getMessage();
return $msg;
}
if ($results->getItems()) {
$responceEvents = array_merge($responceEvents, $results->getItems());
$pageToken = $results->getNextPageToken();
}
} while ($pageToken);
$client->fetchAccessTokenWithRefreshToken($client->getRefreshToken());
returning below error
Array
(
[error] => unauthorized_client
[error_description] => Unauthorized
)
debugged code line by line $client->getRefreshToken() also contain value which i am passing here $client->setAccessToken($accessToken); exactly same code working fine in my browser file and not working in my cron.
401 unauthorized_client
Means that your code has not been authorized. If you want to access private user data you will need to be logged in. Your code needs an access token in order to access the Google api.
What you should be doing is authorizing your code once using Oauth2 in a browser save the refresh token someplace and have the cron job read the refresh token and request a new access token before it runs.
Assuming that you own the account you are trying to insert into you may also want to consider using a service account.
Refresh token
If you are storing the refresh token in the database you must assign it to your client first before running your code here i am doing that with a refresh token stored in a session variable.
$client->refreshToken($_SESSION['refresh_token']);

Google Calendar API not working offline. where do I get refresh token in codeigniter controller

I'm using Google Calendar API to display events on fullcalendar(so using a json object in my view). I'm using codeignitor php framework, and I have a few functions in my controller to create a new client then I use that in the oauth2callback() function to exchange my code for an access_token then I start calling the service in gcalendar() and gcalendar_events. I have set the accessType to offline but that doesn't seem to make me access the events offline. It works great except that I'm redirected to log in again every time the session ends. I don't want that, I want them to display all the time after the session ends. I am trying to use a refresh token in case the access_token expires to see if that would fix the problem.
this is the code in my controller
function getClient() {
$client = new Google_Client();
$client->setApplicationName("DL Calendar");
$client->setAuthConfig('application/client_secrets.json');
$client->addScope('profile');
$client->setIncludeGrantedScopes(true);
$client->setAccessType('offline');
return $client;
}
function gcalendar() {
$this->load->add_package_path(APPPATH . 'vendor/autoload');
$client = $this->getClient();
//$client->setRedirectUri(site_url('calendar/index'));
$client->addScope(Google_Service_Calendar::CALENDAR);
if (isset($_SESSION['access_token']) && $_SESSION['access_token']) {
$client->setAccessToken($_SESSION['access_token']);
$access_token = $_SESSION['access_token'];
$service = new ]Google_Service_Calendar($client);
$calendar = new Google_Service_Calendar_Calendar();
//$calendarList = $service->calendarList->listCalendarList();
$calendar = $service->calendars->get('primary');
$params = array(
'owner_id' => get_current_user_id(),
'title' => get_current_user(). ' ' .'Google Calendar',
'type' => 'gcal',
'url' => $calendar->id,
);
$calendar_id = $this->Calendar_model->add_calendar($params);
redirect('calendar/index');
} else {
$redirect_uri = site_url('calendar/oauth2callback');
header('Location: ' . filter_var($redirect_uri, FILTER_SANITIZE_URL));
}
$this->session->set_flashdata('success', 'Event Successfully Added');
}
function oauth2callback() {
//Build the client object
$client = $this->getClient();
$client->addScope(Google_Service_Calendar::CALENDAR);
$service = new Google_Service_Calendar($client);
$url = parse_url($_SERVER['REQUEST_URI']); parse_str($url['query'], $params);
$code = $params['code'];
//To exchange an authorization code for an access token, use the authenticate method:
if (! isset($code)) {
$auth_url = $client->createAuthUrl();
header('Location: ' . filter_var($auth_url, FILTER_SANITIZE_URL));
} else {
$token = $client->fetchAccessTokenWithAuthCode($code);
$client->setAccessToken($token);
$client->authenticate($code);
$_SESSION['access_token'] = $client->getAccessToken();
$redirect_uri = site_url('calendar/gcalendar');
header('Location: ' . filter_var($redirect_uri, FILTER_SANITIZE_URL));
}
}
function gcalendar_events() {
$client = $this->getClient();
$client->addScope(Google_Service_Calendar::CALENDAR);
// $client->setRedirectUri(site_url('calendar/gcalendar'));
$client->setAccessType('offline'); //need calendar events to appear even if not logged in to google
if (isset($_SESSION['access_token']) && $_SESSION['access_token']) {
$client->setAccessToken($_SESSION['access_token']);
$access_token = $_SESSION['access_token'];
$service = new Google_Service_Calendar($client);
$id = 'primary';
$calendar = new Google_Service_Calendar_Calendar();
$calendar = $service->calendars->get('primary');
$event = new Google_Service_Calendar_Event();
$events = $service->events->listEvents($id);
foreach ($events->getItems() as $event) {
$startTime = strtotime($event->getStart()->dateTime) ;
$endTime = strtotime($event->getEnd()->dateTime);
$start = date('Y-m-d H:i:s', $startTime);
$end = date('Y-m-d H:i:s', $endTime);
$eventsArr[] = array(
'title' => $event->getSummary(),
'start'=> $start,
'end' => $end,
);
}
// Return a single `events` with all the `$eventsArr`
echo json_encode($eventsArr);
}
}
Is the problem in my session ending? or does the access token expire and I need a refresh token? where do I set the refresh token cause I tried putting it in more that one place and I get an error message that refresh token has to be set as part off setAccessToken. I put it all over and still got error messages.
Here is the code I used
if ($client->isAccessTokenExpired()) {
$refresh_token = $client->fetchAccessTokenWithRefreshToken($client->getRefreshToken());
$client->setAccessToken($refresh_token);
$_SESSION['access_token'] = $refresh_token;
$this->load->helper('file');
write_file('application/client_secrets.json', json_encode($client->getAccessToken()));
} else {
$access_token = $_SESSION['access_token'];
}
I just noticed that it once used to say 'grant offline access' in my authorization but now it no longer mentions that while google documentation says
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.
I think this Offline access in OAuth guide might help you. Since you're using PHP, try setting the access type to "offline".
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.

How to refresh token with Google API client?

I've been playing around with the Google Analytics API (V3) and have run into som errors. Firstly, everything is set up correct and worked with my testing account. But when I want to grab data from another profile ID (Same Google Accont/GA Account) I get an 403 Error. The strange thing is that data from some GA accounts will return data whilst other generate this error.
I've revoked the token and authenticated one more time, and now it seems like I can grab data from all of my accounts. Problem solved? Not. As the access key will expire, I will run into the same issue again.
If I have understood things right, one could use the resfreshToken to get a new authenticationTooken.
The problem is, when I run:
$client->refreshToken(refresh_token_key)
the following error is returned:
Error refreshing the OAuth2 token, message: '{ "error" : "invalid_grant" }'
I’ve checked the code behind the refreshToken method and tracked the request back to the “apiOAuth2.php” file. All parameters are sent correctly. The grant_type is hard coded to ‘refresh_token’ within the method, so it’s hard for me to understand what’s wrong. The parameter array looks like this:
Array ( [client_id] => *******-uqgau8uo1l96bd09eurdub26c9ftr2io.apps.googleusercontent.com [client_secret] => ******** [refresh_token] => 1\/lov250YQTMCC9LRQbE6yMv-FiX_Offo79UXimV8kvwY [grant_type] => refresh_token )
The procedure is as follows.
$client = new apiClient();
$client->setClientId($config['oauth2_client_id']);
$client->setClientSecret($config['oauth2_client_secret']);
$client->setRedirectUri($config['oauth2_redirect_uri']);
$client->setScopes('https://www.googleapis.com/auth/analytics.readonly');
$client->setState('offline');
$client->setAccessToken($config['token']); // The access JSON object.
$client->refreshToken($config['refreshToken']); // Will return error here
Is this a bug, or have I completely misunderstood something?
So i finally figured out how to do this. The basic idea is that you have the token you get the first time you ask for authentication. This first token has a refresh token. The first original token expires after an hour. After an hour you have to use the refresh token from the first token to get a new usable token. You use $client->refreshToken($refreshToken) to retrieve a new token. I will call this "temp token." You need to store this temp token as well because after an hour it expires as well and note it does not have a refresh token associated with it. In order to get a new temp token you need to use the method you used before and use the first token's refreshtoken. I have attached code below, which is ugly, but im new at this...
//pull token from database
$tokenquery="SELECT * FROM token WHERE type='original'";
$tokenresult = mysqli_query($cxn,$tokenquery);
if($tokenresult!=0)
{
$tokenrow=mysqli_fetch_array($tokenresult);
extract($tokenrow);
}
$time_created = json_decode($token)->created;
$t=time();
$timediff=$t-$time_created;
echo $timediff."<br>";
$refreshToken= json_decode($token)->refresh_token;
//start google client note:
$client = new Google_Client();
$client->setApplicationName('');
$client->setScopes(array());
$client->setClientId('');
$client->setClientSecret('');
$client->setRedirectUri('');
$client->setAccessType('offline');
$client->setDeveloperKey('');
//resets token if expired
if(($timediff>3600)&&($token!=''))
{
echo $refreshToken."</br>";
$refreshquery="SELECT * FROM token WHERE type='refresh'";
$refreshresult = mysqli_query($cxn,$refreshquery);
//if a refresh token is in there...
if($refreshresult!=0)
{
$refreshrow=mysqli_fetch_array($refreshresult);
extract($refreshrow);
$refresh_created = json_decode($token)->created;
$refreshtimediff=$t-$refresh_created;
echo "Refresh Time Diff: ".$refreshtimediff."</br>";
//if refresh token is expired
if($refreshtimediff>3600)
{
$client->refreshToken($refreshToken);
$newtoken=$client->getAccessToken();
echo $newtoken."</br>";
$tokenupdate="UPDATE token SET token='$newtoken' WHERE type='refresh'";
mysqli_query($cxn,$tokenupdate);
$token=$newtoken;
echo "refreshed again";
}
//if the refresh token hasn't expired, set token as the refresh token
else
{
$client->setAccessToken($token);
echo "use refreshed token but not time yet";
}
}
//if a refresh token isn't in there...
else
{
$client->refreshToken($refreshToken);
$newtoken=$client->getAccessToken();
echo $newtoken."</br>";
$tokenupdate="INSERT INTO token (type,token) VALUES ('refresh','$newtoken')";
mysqli_query($cxn,$tokenupdate);
$token=$newtoken;
echo "refreshed for first time";
}
}
//if token is still good.
if(($timediff<3600)&&($token!=''))
{
$client->setAccessToken($token);
}
$service = new Google_DfareportingService($client);
The problem is in the refresh token:
[refresh_token] => 1\/lov250YQTMCC9LRQbE6yMv-FiX_Offo79UXimV8kvwY
When a string with a '/' gets json encoded, It is escaped with a '\', hence you need to remove it.
The refresh token in your case should be:
1/lov250YQTMCC9LRQbE6yMv-FiX_Offo79UXimV8kvwY
What i'm assuming you've done is that you've printed the json string which google sent back and copied and pasted the token into your code because if you json_decode it then it will correctly remove the '\' for you!
here is the snippet to set token, before that make sure the access type should be set to offline
if (isset($_GET['code'])) {
$client->authenticate();
$_SESSION['access_token'] = $client->getAccessToken();
}
To refresh token
$google_token= json_decode($_SESSION['access_token']);
$client->refreshToken($google_token->refresh_token);
this will refresh your token, you have to update it in session for that you can do
$_SESSION['access_token']= $client->getAccessToken()
The access type should be set to offline. state is a variable you set for your own use, not the API's use.
Make sure you have the latest version of the client library and add:
$client->setAccessType('offline');
See Forming the URL for an explanation of the parameters.
The answer posted by #uri-weg worked for me but as I did not find his explanations very clear, let me reword it a little.
During the first access permission sequence, in the callback, when you get to the point where you receive an authentication code, you must save the access token and the refresh token as well.
The reason is google api sends you an access token with a refresh token only when prompting for access permission. The next access tokens will be sent without any refresh token (unless you use the approval_prompt=force option).
The refresh token you received the first time stays valid until the user revokes access permission.
In simplistic php, an example of the callback sequence would be:
// init client
// ...
$authCode = $_GET['code'];
$accessToken = $client->authenticate($authCode);
// $accessToken needs to be serialized as json
$this->saveAccessToken(json_encode($accessToken));
$this->saveRefreshToken($accessToken['refresh_token']);
And later on, in simplistic php, the connection sequence would be:
// init client
// ...
$accessToken = $this->loadAccessToken();
// setAccessToken() expects json
$client->setAccessToken($accessToken);
if ($client->isAccessTokenExpired()) {
// reuse the same refresh token
$client->refreshToken($this->loadRefreshToken());
// save the new access token (which comes without any refresh token)
$this->saveAccessToken($client->getAccessToken());
}
Here is the code which I am using in my project and it is working fine:
public function getClient(){
$client = new Google_Client();
$client->setApplicationName(APPNAME); // app name
$client->setClientId(CLIENTID); // client id
$client->setClientSecret(CLIENTSECRET); // client secret
$client->setRedirectUri(REDIRECT_URI); // redirect uri
$client->setApprovalPrompt('auto');
$client->setAccessType('offline'); // generates refresh token
$token = $_COOKIE['ACCESSTOKEN']; // fetch from cookie
// if token is present in cookie
if($token){
// use the same token
$client->setAccessToken($token);
}
// this line gets the new token if the cookie token was not present
// otherwise, the same cookie token
$token = $client->getAccessToken();
if($client->isAccessTokenExpired()){ // if token expired
$refreshToken = json_decode($token)->refresh_token;
// refresh the token
$client->refreshToken($refreshToken);
}
return $client;
}
Had the same issue; my script that worked yesterday, for some odd reason did not today. No changes.
Apparently this was because my system clock was off by 2.5 (!!) seconds, syncing with NTP fixed it.
See also: https://code.google.com/p/google-api-php-client/wiki/OAuth2#Solving_invalid_grant_errors
Sometimes Refresh Token i not generated by using $client->setAccessType ("offline");.
Try this:
$client->setAccessType ("offline");
$client->setApprovalPrompt ("force");
FYI: The 3.0 Google Analytics API will automatically refresh the access token if you have a refresh token when it expires so your script never needs refreshToken.
(See the Sign function in auth/apiOAuth2.php)
You need to save the access token to file or database as a json string during the initial authorization request, and set the access type to offline $client->setAccessType("offline")
Then, during subsequent api requests, grab the access token from your file or db and pass it to the client:
$accessToken = json_decode($row['token'], true);
$client->setAccessToken($accessToken);
Now you need to check if the token has expired:
if ($client->isAccessTokenExpired()) {
// access token has expired, use the refresh token to obtain a new one
$client->fetchAccessTokenWithRefreshToken($client->getRefreshToken());
// save the new token to file or db
// ...json_encode($client->getAccessToken())
The fetchAccessTokenWithRefreshToken() function will do the work for you and provide a new access token, save it back to your file or database.
I used the example by smartcodes with the current version of the Google API, but that one didn't work. I think his API is too outdated.
So, I just wrote my own version, based on one of the API examples... It outputs access token, request token, token type, ID token, expiration time and creation time as strings
If your client credentials and developer key are correct, this code should work out of the box.
<?php
// Call set_include_path() as needed to point to your client library.
require_once 'google-api-php-client/src/Google_Client.php';
require_once 'google-api-php-client/src/contrib/Google_Oauth2Service.php';
session_start();
$client = new Google_Client();
$client->setApplicationName("Get Token");
// Visit https://code.google.com/apis/console?api=plus to generate your
// oauth2_client_id, oauth2_client_secret, and to register your oauth2_redirect_uri.
$oauth2 = new Google_Oauth2Service($client);
if (isset($_GET['code'])) {
$client->authenticate($_GET['code']);
$_SESSION['token'] = $client->getAccessToken();
$redirect = 'http://' . $_SERVER['HTTP_HOST'] . $_SERVER['PHP_SELF'];
header('Location: ' . filter_var($redirect, FILTER_SANITIZE_URL));
return;
}
if (isset($_SESSION['token'])) {
$client->setAccessToken($_SESSION['token']);
}
if (isset($_REQUEST['logout'])) {
unset($_SESSION['token']);
$client->revokeToken();
}
?>
<!doctype html>
<html>
<head><meta charset="utf-8"></head>
<body>
<header><h1>Get Token</h1></header>
<?php
if ($client->getAccessToken()) {
$_SESSION['token'] = $client->getAccessToken();
$token = json_decode($_SESSION['token']);
echo "Access Token = " . $token->access_token . '<br/>';
echo "Refresh Token = " . $token->refresh_token . '<br/>';
echo "Token type = " . $token->token_type . '<br/>';
echo "Expires in = " . $token->expires_in . '<br/>';
echo "ID Token = " . $token->id_token . '<br/>';
echo "Created = " . $token->created . '<br/>';
echo "<a class='logout' href='?logout'>Logout</a>";
} else {
$authUrl = $client->createAuthUrl();
print "<a class='login' href='$authUrl'>Connect Me!</a>";
}
?>
</body>
</html>
Google has made some changes since this question was originally posted.
Here is my currently working example.
public function update_token($token){
try {
$client = new Google_Client();
$client->setAccessType("offline");
$client->setAuthConfig(APPPATH . 'vendor' . DIRECTORY_SEPARATOR . 'google' . DIRECTORY_SEPARATOR . 'client_secrets.json');
$client->setIncludeGrantedScopes(true);
$client->addScope(Google_Service_Calendar::CALENDAR);
$client->setAccessToken($token);
if ($client->isAccessTokenExpired()) {
$refresh_token = $client->getRefreshToken();
if(!empty($refresh_token)){
$client->fetchAccessTokenWithRefreshToken($refresh_token);
$token = $client->getAccessToken();
$token['refresh_token'] = json_decode($refresh_token);
$token = json_encode($token);
}
}
return $token;
} catch (Exception $e) {
$error = json_decode($e->getMessage());
if(isset($error->error->message)){
log_message('error', $error->error->message);
}
}
}
I have a same problem with google/google-api-php-client v2.0.0-RC7
and after search for 1 hours, i solved this problem using json_encode
like this:
if ($client->isAccessTokenExpired()) {
$newToken = json_decode(json_encode($client->getAccessToken()));
$client->refreshToken($newToken->refresh_token);
file_put_contents(storage_path('app/client_id.txt'), json_encode($client->getAccessToken()));
}
This here works very good, maybe it could help anybody:
index.php
session_start();
require_once __DIR__.'/client.php';
if(!isset($obj->error) && isset($_SESSION['access_token']) && $_SESSION['access_token'] && isset($obj->expires_in)) {
?>
<!DOCTYPE html>
<html>
<head>
<title>Google API Token Test</title>
<meta charset='utf-8' />
<script src="https://code.jquery.com/jquery-1.12.4.js"></script>
<script>
search('Music Mix 2010');
function search(q) {
$.ajax({
type: 'GET',
url: 'action.php?q='+q,
success: function(data) {
if(data == 'refresh') location.reload();
else $('#response').html(JSON.stringify(JSON.parse(data)));
}
});
}
</script>
</head>
<body>
<div id="response"></div>
</body>
</html>
<?php
}
else header('Location: '.filter_var('https://'.$_SERVER['HTTP_HOST'].dirname($_SERVER['PHP_SELF']).'/oauth2callback.php', FILTER_SANITIZE_URL));
?>
oauth2callback.php
require_once __DIR__.'/vendor/autoload.php';
session_start();
$client = new Google_Client();
$client->setAuthConfigFile('auth.json');
$client->setAccessType('offline');
$client->setApprovalPrompt('force');
$client->setRedirectUri('https://'.filter_var($_SERVER['HTTP_HOST'].$_SERVER['PHP_SELF'], FILTER_SANITIZE_URL));
$client->addScope(Google_Service_YouTube::YOUTUBE_FORCE_SSL);
if(isset($_GET['code']) && $_GET['code']) {
$client->authenticate(filter_var($_GET['code'], FILTER_SANITIZE_STRING));
$_SESSION['access_token'] = $client->getAccessToken();
$_SESSION['refresh_token'] = $_SESSION['access_token']['refresh_token'];
setcookie('refresh_token', $_SESSION['refresh_token'], time()+60*60*24*180, '/', filter_var($_SERVER['HTTP_HOST'], FILTER_SANITIZE_URL), true, true);
header('Location: '.filter_var('https://'.$_SERVER['HTTP_HOST'].dirname($_SERVER['PHP_SELF']), FILTER_SANITIZE_URL));
exit();
}
else header('Location: '.filter_var($client->createAuthUrl(), FILTER_SANITIZE_URL));
exit();
?>
client.php
// https://developers.google.com/api-client-library/php/start/installation
require_once __DIR__.'/vendor/autoload.php';
$client = new Google_Client();
$client->setAuthConfig('auth.json');
$client->setAccessType('offline');
$client->setApprovalPrompt('force');
$client->addScope(Google_Service_YouTube::YOUTUBE_FORCE_SSL);
// Delete Cookie Token
#setcookie('refresh_token', #$_SESSION['refresh_token'], time()-1, '/', filter_var($_SERVER['HTTP_HOST'], FILTER_SANITIZE_URL), true, true);
// Delete Session Token
#unset($_SESSION['refresh_token']);
if(isset($_SESSION['refresh_token']) && $_SESSION['refresh_token']) {
$client->refreshToken($_SESSION['refresh_token']);
$_SESSION['access_token'] = $client->getAccessToken();
}
elseif(isset($_COOKIE['refresh_token']) && $_COOKIE['refresh_token']) {
$client->refreshToken($_COOKIE['refresh_token']);
$_SESSION['access_token'] = $client->getAccessToken();
}
$url = 'https://www.googleapis.com/oauth2/v1/tokeninfo?access_token='.urlencode(#$_SESSION['access_token']['access_token']);
$curl_handle = curl_init();
curl_setopt($curl_handle, CURLOPT_URL, $url);
curl_setopt($curl_handle, CURLOPT_CONNECTTIMEOUT, 2);
curl_setopt($curl_handle, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($curl_handle, CURLOPT_USERAGENT, 'Google API Token Test');
$json = curl_exec($curl_handle);
curl_close($curl_handle);
$obj = json_decode($json);
?>
action.php
session_start();
require_once __DIR__.'/client.php';
if(isset($obj->error)) {
echo 'refresh';
exit();
}
elseif(isset($_SESSION['access_token']) && $_SESSION['access_token'] && isset($obj->expires_in) && isset($_GET['q']) && !empty($_GET['q'])) {
$client->setAccessToken($_SESSION['access_token']);
$service = new Google_Service_YouTube($client);
$response = $service->search->listSearch('snippet', array('q' => filter_input(INPUT_GET, 'q', FILTER_SANITIZE_SPECIAL_CHARS), 'maxResults' => '1', 'type' => 'video'));
echo json_encode($response['modelData']);
exit();
}
?>
I use google-api-php-client v2.2.2 I get a new token with fetchAccessTokenWithRefreshToken(); if function call without params, it returns an updated access token and the refreshed token is not lost.
if ($client->getAccessToken() && $client->isAccessTokenExpired()) {
$new_token=$client->fetchAccessTokenWithRefreshToken();
$token_data = $client->verifyIdToken();
}
use the following code snippet to get your refresh token
<?php
require_once 'src/apiClient.php';
require_once 'src/contrib/apiTasksService.php';
$client = new apiClient();
$client->setAccessType('offline');
$tasksService = new apiTasksService($client);
$auth = $client->authenticate();
$token = $client->getAccessToken();
// the refresh token
$refresh_token = $token['refresh_token'];
?>
According to Authentication on google: OAuth2 keeps returning 'invalid_grant'
"You should reuse the access token you get after the first successful authentication. You will get an invalid_grant error if your previous token has not expired yet. Cache it somewhere so you can reuse it."
hope it helps
I got into this issue and I found this to be the simplest and cleanest way to get proper token.
public function authenticate()
{
$access_token = 'OLD_TOKEN';
$refresh_token = 'OLD_TOKEN';
if ($access_token) {
$this->client->setAccessToken($access_token);
}
if ($this->client->isAccessTokenExpired()) {
$this->client->refreshToken($refresh_token);
}
}
I have client as property on the class that's why I am using $this->client.

Categories