Following code is working fine with the first access token but after I refresh the access token with refresh token, this throws UNAUTHENTICATED error.
$client = new GoogleClient();
$client->setClientId('XXXXXX');
$client->setClientSecret('XXXXX');
$client->setAccessToken('ya20.xxxxxx);
$client->refreshToken(1//xxxxxxx);
$calendarId = 'primary';
$optParams = array(
'maxResults' => 10,
'orderBy' => 'startTime',
'singleEvents' => TRUE,
'timeMin' => date('c'),
);
$calendarService = new Calendar($client);
$results = $calendarService->events->listEvents($calendarId, $optParams);
I am updating the access token with refresh token in the following way -
$client = new GoogleClient();
$client->setClientId('XXXXXX');
$client->setClientSecret('XXXXX');
$client->setAccessToken('ya20.xxxxxx);
$newToken = [];
$client->fetchAccessTokenWithRefreshToken('1//xxxx');
$newTokens = $client->getAccessToken();
if (isset($newTokens['access_token'])) {
$accessToken = $newTokens['access_token'];
$newToken['token'] = $accessToken;
}
if (isset($accessToken['refresh_token'])) {
$refreshToken = $newTokens['refresh_token'];
$newToken['refresh_token'] = $refreshToken;
}
This is returning the access token but after using this token in the above code I am getting 401 UNAUTHENTICATED Error.
I am fetching access token and refresh token with Laravel/Socialite package
$scopes = [
'https://www.googleapis.com/auth/userinfo.profile',
'https://www.googleapis.com/auth/userinfo.email',
'https://www.googleapis.com/auth/calendar',
];
return Socialite::driver('google')
->scopes($scopes)
->with([
"access_type" => "offline",
"prompt" => "consent select_account"])
->redirect();
I would question wither or not your refresh token is valid.
This is the method i use.
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();
}
}
validate refresh token
To use a refresh token is a HTTP Post call. If the call isnt working with php it would sugest to me that this is not a valid.
If You want to try and validate it directly using something like postman the call would be like this.
HTTP POST https://accounts.google.com/o/oauth2/token
client_id={ClientId}&client_secret={ClientSecret}&refresh_token=1/ffYmfI0sjR54Ft9oupubLzrJhD1hZS5tWQcyAvNECCA&grant_type=refresh_token
Related
I'm integrating Gmail OAuth2 in Suite CRM. Everything is working fine on my local. I'm getting the refresh token on the first API call but on the production, I am getting the token/access token but getting an empty refresh token. Is this related to some Gmail permissions that are permitting my production application and allowing my local or something else?
Reference Code:
$params = [
'clientId' => $clientId,
'clientSecret' => $clientSecret,
'redirectUri' => $redirectUri,
'accessType' => 'offline',
];
$options = [];
session_start();
$provider = new Google($params);
$options = [
'scope' => [
'https://mail.google.com/'
]
];
if (null === $provider) {
exit('Provider missing');
}
if (!empty($_GET['error'])) {
// Got an error, probably user denied access
exit('Got error: ' . htmlspecialchars($_GET['error'], ENT_QUOTES, 'UTF-8'));
}
if (!isset($_GET['code'])) {
// If we don't have an authorization code then get one
$authUrl = $provider->getAuthorizationUrl($options);
$_SESSION['oauth2state'] = $provider->getState();
header('Location: ' . $authUrl);
exit;
// Check given state against previously stored one to mitigate CSRF attack
} elseif (empty($_GET['state']) || ($_GET['state'] !== $_SESSION['oauth2state'])) {
unset($_SESSION['oauth2state']);
unset($_SESSION['provider']);
exit('Invalid state');
} else {
// unset($_SESSION['provider']);
$token = $provider->getAccessToken(
'authorization_code',
[
'code' => $_GET['code']
]
); //Gets Token
$refresh_token = $token->getRefreshToken(); // Get Null in response
// Use this to interact with an API on the users behalf
$access_token = $token->getToken(); //access token
// Unix timestamp at which the access token expires
$access_tkn_expiration = $token->getExpires(); //access token expiry
With some languages mainly scripting languages which are web based, Google does not return a new refresh token every time. They assume that you have saved your refresh token.
To force a new one you can do a revoke on the users access token, which will cause the user to revoke your access of their data. Then the next time the user logs in they will be prompted to grant you access and you should get a new refresh token.
All this is assuming that you are requesting the offline scope which i cant see from your code.
Oauth2Authentication.php
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();
}
}
I've been block for two days facing 0auth problems after 1 hour on Youtube api. 401 credential error.
$OAUTH2_CLIENT_ID = 'XXXXX';
$OAUTH2_CLIENT_SECRET = 'XXXXX';
$client = new Google_Client();
$client->setAccessType('offline');
$client->setApprovalPrompt('force');
$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);
$service = new Google_Service_YouTube($client);
if (isset($_GET['code'])){
$accessToken = $client->fetchAccessTokenWithAuthCode($_GET['code']);
$client->setAccessToken($accessToken);
$test=$client->getAccessToken();
//TEST REFRESH TOKEN
print_r($test);
sleep(10);
$client->fetchAccessTokenWithRefreshToken($client->getRefreshToken());
$test=$client->getAccessToken();
print_r($test);
}
// Check to ensure that the access token was successfully acquired.
if ($client->getAccessToken()) {
...
foreach ($files as $file){
if ($client->isAccessTokenExpired()) {
$client->fetchAccessTokenWithRefreshToken($client->getRefreshToken());
file_put_contents($credentialsPath, json_encode($client->getAccessToken()));
} // end if token expired
====CALL YOUTUBE API HERE IN FOREACH LOOP ===
}// end foreach files
}
The result of my test code show me the access token didn't change at all after provided refresh token, even the expiration time have not dicreased 'expires_in'
So that's why I'm facing a credential error after an hour ... I don't know what's wrong with my code, please help me.
This the result of my test code after getting access code, so as you can see the 'new' access token is similar at the previous one and I already tried to use encode_json too on parameter of setAccessToken() and fetchAccessTokenWithRefreshToken(). Not getting error but result still same ...
Array ( [access_token] => ya29.GlvQBuGBfZDQn3E8HWd4wfSbb0hLHsYVGzPBE0boJuB4ien5pcsOGqXlkEyOU7mevDLOGOWbuakTyTiAUVf2bkxNwZXX [expires_in] => 3600 [refresh_token] => 1/KEgjy2t9kTNwCXk-ZtMTSzPSS2xl4XX [scope] => https://www.googleapis.com/auth/youtube [token_type] => Bearer [created] => 1552891085 )
Array ( [access_token] => ya29.GlvQBuGBfZDQn3E8HWd4wfSbb0hLHsYVGzPBE0boJuB4ien5pcsOGqXlkEyOU7mevDLOGOWbuakTyTiAUVf2bkxNwZXX [expires_in] => 3600 [refresh_token] => 1/KEgjy2t9kTNwCXk-ZtMTSzPSS2xl4XX [scope] => https://www.googleapis.com/auth/youtube [token_type] => Bearer [created] => 1552891085 )
Thanks you
Access tokens expire after one hour this is how they work. Once the access token has expired you should run your code and fetch a new access token. Fetching a new access token before it expires will result in the same access token.
Your access token was created at created (tip epoch converter) add 3600 (seconds) to find out when it expires.
1552891085 <--- Monday, March 18, 2019 6:38:05 AM
The only thing i can see wrong with your code is Your fetching the access token but not actually using it Oauth2Authentication.php
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();
}
}
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");
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.
I'm trying to connect my webapp to google drive. So I'm using PHP with official Github PHP client code [ https://github.com/google/google-api-php-client/tree/v1-master ].
I followed the quickstart [ https://developers.google.com/drive/v2/web/quickstart/php ] for v2, because PHP client is for v2 only.
Then I added a line to request offline access. [See https://developers.google.com/identity/protocols/OAuth2WebServer#offline]
My app code, developed using Yii 1, but it's not important, is:
$client = new Google_Client();
$client->setApplicationName("Google Drive Client");
$client->addScope(Google_Service_Drive::DRIVE_METADATA_READONLY);
$client->setRedirectUri( Yii::app()->createAbsoluteUrl("site/googleApiLoginCallback") );
$client->setAuthConfigFile(CLIENT_SECRET_PATH);
$client->setAccessType('offline');
if (file_exists(CREDENTIALS_PATH)) {
$accessToken = file_get_contents(CREDENTIALS_PATH);
} else {
// Request authorization from the user.
$auth_url = $client->createAuthUrl();
header('Location: ' . filter_var($auth_url, FILTER_SANITIZE_URL));
Yii::app()->end();
}
$client->setAccessToken($accessToken);
// Refresh the token if it's expired.
if ($client->isAccessTokenExpired()) {
$refresh_token = $client->getRefreshToken();
// CVarDumper::dump($refresh_token,2,true);
$client->refreshToken($refresh_token);
file_put_contents(CREDENTIALS_PATH, $client->getAccessToken());
}
return $client;
This is the code for handling the OAuth callback. I simply set the access token received, then redirect to the page.
public function actionGoogleApiLoginCallback($code)
{
$client = new Google_Client();
$client->setApplicationName("Google Drive Client");
$client->addScope(Google_Service_Drive::DRIVE_METADATA_READONLY);
$client->setRedirectUri( Yii::app()->createAbsoluteUrl("site/googleApiLoginCallback") );
$client->setAuthConfigFile(CLIENT_SECRET_PATH);
$client->setAccessType('offline');
$accessToken = $client->authenticate($code);
if(!file_exists(dirname(CREDENTIALS_PATH))) {
mkdir(dirname(CREDENTIALS_PATH), 0700, true);
}
file_put_contents(CREDENTIALS_PATH, $accessToken);
$preGoogleApiLoginRoute = Yii::app()->user->getState("preGoogleApiLoginRoute", null);
if ($preGoogleApiLoginRoute)
{
$this->redirect(array( $preGoogleApiLoginRoute ));
} else {
$this->redirect(array("site/index"));
}
}
When user the first time access the page, my webapp sucessfully redirect to Google Login; user do login, and Google redirect user to my website at site/googleApiLoginCallback. I set the received code as accessToken and redirect user to the page of webapp he come from.
It works.
BUT: After a while, when user came back to the page, tyhe token is expired. When it's executed the $client->getRefreshToken(), it returns a null, so $client->refreshToken() throw the following error because of missing refresh token
Error refreshing the OAuth2 token, message: '{ "error" : "invalid_request", "error_description" : "Missing required parameter: refresh_token" }'
What am I missing or doing wrong?
For reference: this is my json access token. As you can see I've not a field named 'refreshToken' as I expect
{"access_token":"...hiddden...","token_type":"Bearer","expires_in":3600,"created":1453759023}
From this StackOverflow question I see that statement
in order to obtain a new refresh_token after already receiving one, you will need to send your user back through the prompt, which you can do by setting approval_prompt to force.
It pointed to this old blog post by Google.
So I added
$client->setApprovalPrompt('force');
after
$client->setAccessType('offline');
And now I've the resfresh token.
I am using a bit different logic, but it works... :-)
Instead of:
...
$accessToken = file_get_contents(CREDENTIALS_PATH);
...
$client->setAccessToken($accessToken);
if ($client->isAccessTokenExpired()) {
$refresh_token = $client->getRefreshToken();
$client->refreshToken($refresh_token);
file_put_contents(CREDENTIALS_PATH, $client->getAccessToken());
}
...
I do:
...
$accessToken = file_get_contents(CREDENTIALS_PATH);
...
$client->setAccessToken($accessToken);
if (!$client->getAccessToken()) {
die('invalid access token in ' . CREDENTIALS_PATH);
}
if ($client->isAccessTokenExpired()) {
$refresh_token = json_decode($accessToken)->refresh_token;
$client->refreshToken($refresh_token);
}
... now we are authenticated ...