Google Calendar API. Service Account. Access Token Expired. Refresh AT without RT - google-calendar-api

I'm trying to get events from my Google Calendar using Service Account. I received Access Token:
{"access_token":"ya29.AHES6ZR9o2-cut-Gg","expires_in":3600,"created":1366631471}
Now this token is expired and when I trying to get events, I get an error:
The OAuth 2.0 access token has expired, and a refresh token is not available. Refresh tokens are not returned for responses that were auto-approved.
I tried to find way to get a new access token in API documentation, but did not find anything suitable. And now I have a question: How I must refresh my access token?
Code that I use to access the calendar:
session_start();
require_once '../../src/Google_Client.php';
require_once '../../src/contrib/Google_CalendarService.php';
define('SERVICE_ACCOUNT_NAME', 'numbers-and-letters#developer.gserviceaccount.com');
define('CLIENT_ID', 'numbers-and-letters.apps.googleusercontent.com');
define('KEY_FILE', '../../key.p12');
$client = new Google_Client();
$client->setApplicationName("app name");
$client->setUseObjects(true);
$client->setClientID(CLIENT_ID);
$key = file_get_contents(KEY_FILE);
if (isset($_SESSION['token']))
{
$client->setAccessToken($_SESSION['token']);
$client->setaccessType('offline');
}
else
{
$client->setAssertionCredentials(new Google_AssertionCredentials(
SERVICE_ACCOUNT_NAME,
array('https://www.googleapis.com/auth/calendar.readonly'),
$key
));
}
try
{
$cal = new Google_CalendarService($client);
$events = $cal->events->listEvents('numbers-and-letters#group.calendar.google.com');
print_r($events);
} catch (Exception $e) echo $e->getMessage();
if ($client->getAccessToken()) {
$_SESSION['token'] = $client->getAccessToken();
}

I solved! To refresh Access Token without Refresh Token you want to call Google_Client class's method revokeToken()

The recommended way to refresh token is:
if ($client->isAccessTokenExpired())
{
$client->getAuth()->refreshTokenWithAssertion();
}

Related

Call the wordpress logout interface with a cookie, but the front end is not logged out

I tried to use RestTemplate to carry cookies and request a custom Wordpress logout interface, but the front end did not log out. When I directly accessed the logout url in the browser, I was able to logout successfully.
Here is my RestTemplate code:
public void logoutOfWordpress(HttpServletRequest request, HttpServletResponse response) {
Cookie[] cookies = request.getCookies();
String url = wordpressProper.getRestUrl() + "/community-rest/logout";
HttpHeaders httpHeaders = buildHeaderByCookies(cookies);
ResponseEntity<String> exchange = restTemplate.exchange(url, HttpMethod.GET, new HttpEntity<>(httpHeaders), String.class);
Object body = exchange.getBody();
}
This is the WordPress logout interface:
add_action( 'rest_api_init', 'community_rest_logout');
function community_rest_logout() {
register_rest_route(
'community-rest',
'/logout/',
array(
'methods' => 'GET',
'callback' => 'logout'
)
);
}
function logout() {
wp_logout();
wp_redirect('http://192.168.100.5:8888/sign_in');
exit;
}
I tried to use Postman to call this logout interface and found that there is
Set-Cookie in the response header. It seems that this is necessary for logout. It seems that I can directly set the Max-Age of the cookie to
0 to achieve the purpose of logout without having to to call the logout interface
.
What are you using for user login? Sessions are not started internally in wordpress rest api. You should be able to get the user_id if the user is logged in on your logout endpoint. Can you get this?
Generally, nonce is used on rest for unregistered users. You need to send the nonce value in header or parameter. For registered users, you need to authenticate cookies or you can use some rest api authentication plugins.
For a simple hack you can use this at the top of your endpoints:
public function init_cookie() {
$userID = wp_validate_logged_in_cookie(false);
if($userID) {
wp_set_current_user($userID);
}
}
I would also suggest defining your code in a class or prefixing the names of the functions. The logout function is a very generic name. If this is just for an example, ignore what I said.

Xamarin.Forms get new Token when session is over

I have this scenario: Xamarin.Forms App connected with Web Api 2. I make all requests and get the data i want. Now when the session token expires, i need to refresh the token but don't logout the user. The user don't need to know when token is refreshed. How to organize this, add in every request if statement when i send it and check if token expires.
This is one of my requests:
public async Task<User> GetProfileSetup()
{
try
{
if (CrossConnectivity.Current.IsConnected)
{
string token = DependencyService.Get<ISharedFunctions>().GetAccessToken();
client.DefaultRequestHeaders.Clear();
client.DefaultRequestHeaders.Add("Accept", "application/json");
client.DefaultRequestHeaders.Add("Authorization", "Bearer " + token);
var response = await client.GetAsync(#"api/Profile/GetProfilSetup");
if (response.IsSuccessStatusCode)
{
string jsonMessage;
using (Stream responseStream = await response.Content.ReadAsStreamAsync())
{
jsonMessage = new StreamReader(responseStream).ReadToEnd();
}
User user = JsonConvert.DeserializeObject<User>(jsonMessage);
return user;
}
else
{
var m = response.Content.ToString();
return null;
}
}
else
{
return null;
}
}
catch (Exception ex)
{
Debug.WriteLine(ex);
string error = ex.Message;
return null;
}
}
P.S I have Methods for GetToken and RefreshToken in my Api and they are working, just how to organize Refreshing ?
It really depends on what libraries are you using on your project.
But let's say you're using plain c# to handled your HTTP calls.
[OPTION 1] Polly
I can recommend you looking at Polly
It's a great library with a lot of features. You can use the Retry policy to handled expired tokens:
var _unauthorizedPolicy = Policy
.Handle<Exception>(ex => ex.StatusCode == HttpStatusCode.Unauthorized) // check here for your exception to be the right one
.RetryAsync(3, async (exception, retryCount, context) =>
{
try
{
var token = await _authService.RefreshToken();
// save the new token or whatever you need to store it
}
catch (Exception ex)
{
// RefreshToken failed, you should probably sign out the user
SignOut();
}
});
What this does is that Polly will try to execute your normal HTTP call and in case it fails and the cause is specified in Handle, then a retry mechanism is fired that will try to refresh the token and then retry your request. In the end, in case the token cannot be refreshed, you sign out the user. Of course, all this can be customized, check Polly's documentation is pretty well written.
Please note that inside Handle<T> you must put the right exception. I just used Exception as a placeholder since I'm not sure what Exception is thrown in your case.
Then you would call your method with this policy:
var result = await _unauthorizedPolicy.ExecuteAsync(() => GetProfileSetup())
And you can reuse that policy for any call, no need to create it every time.
[OPTION 2] DelegatingHandler
I will like here another StackOverflow answer:
How to Refresh a token using IHttpClientFactory
Basically you can intercept every HTTP call made via a HttpClient and refresh/add a token to your requests.
Note that that answer does not obligate you to use IHttpClientFactory, it also works for a simple HttpClient.
Also a little bit off-topic. You might want to look up for libraries to handle htt calls such as Retrofit. It will really reduce the amount of boilerplate code.

Google Calendar Server to Server

I use to make appointments a calendar based on FULLCALENDAR with backup of the rdv in a database mysql on my own local server. So far, so good. I wish I could synchronize the rdvs thus taken, on the google calendar and inversely be able to import into my database mysql the rdvs taken on the account of google on line.
I tried to understand and read the API doc of google calendar here and here
I only get error messages.
I tried to do it directly in fullcalendar but the calendar must be declared in public, something I can not do.
The ideal would be to be able to make server calls in php (login and password of google clandier backup in local mysql database), for which I create my API key and a service account, but again I do not get only error messages.
Would there be a link or a really functional tutorial that I could use to learn
I tried with this link but never succeeded
thanks in advance.
EDIT : I try this.
quickstart.php
<?php
require_once __DIR__ . '/vendor/autoload.php';
define('APPLICATION_NAME', 'Google Calendar API PHP Quickstart');
define('CREDENTIALS_PATH', '~/.credentials/calendar-php-quickstart.json');
define('CLIENT_SECRET_PATH', __DIR__ . '/client_id.json');
// If modifying these scopes, delete your previously saved credentials
// at ~/.credentials/calendar-php-quickstart.json
define('SCOPES', implode(' ', array(
Google_Service_Calendar::CALENDAR_READONLY)
));
if (php_sapi_name() != 'cli') {
throw new Exception('This application must be run on the command line.');
}
/**
* Returns an authorized API client.
* #return Google_Client the authorized client object
*/
function getClient() {
$client = new Google_Client();
$client->setApplicationName(APPLICATION_NAME);
$client->setScopes(SCOPES);
$client->setAuthConfig(CLIENT_SECRET_PATH);
$client->setAccessType('offline');
// Load previously authorized credentials from a file.
$credentialsPath = expandHomeDirectory(CREDENTIALS_PATH);
if (file_exists($credentialsPath)) {
$accessToken = json_decode(file_get_contents($credentialsPath), true);
} else {
// 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);
// Store the credentials to disk.
if(!file_exists(dirname($credentialsPath))) {
mkdir(dirname($credentialsPath), 0700, true);
}
file_put_contents($credentialsPath, json_encode($accessToken));
printf("Credentials saved to %s\n", $credentialsPath);
}
$client->setAccessToken($accessToken);
// Refresh the token if it's expired.
if ($client->isAccessTokenExpired()) {
$client->fetchAccessTokenWithRefreshToken($client->getRefreshToken());
file_put_contents($credentialsPath, json_encode($client->getAccessToken()));
}
return $client;
}
/**
* Expands the home directory alias '~' to the full path.
* #param string $path the path to expand.
* #return string the expanded path.
*/
function expandHomeDirectory($path) {
$homeDirectory = getenv('HOME');
if (empty($homeDirectory)) {
$homeDirectory = getenv('HOMEDRIVE') . getenv('HOMEPATH');
}
return str_replace('~', realpath($homeDirectory), $path);
}
// Get the API client and construct the service object.
$client = getClient();
$service = new Google_Service_Calendar($client);
// Print the next 10 events on the user's calendar.
$calendarId = 'primary';
$optParams = array(
'maxResults' => 10,
'orderBy' => 'startTime',
'singleEvents' => TRUE,
'timeMin' => date('c'),
);
$results = $service->events->listEvents($calendarId, $optParams);
if (count($results->getItems()) == 0) {
print "No upcoming events found.\n";
} else {
print "Upcoming events:\n";
foreach ($results->getItems() as $event) {
$start = $event->start->dateTime;
if (empty($start)) {
$start = $event->start->date;
}
printf("%s (%s)\n", $event->getSummary(), $start);
}
}
php quickstart.php
PHP Fatal error: Uncaught InvalidArgumentException: missing the required redirect URI in /Users/krislec/Desktop/vendor/google/auth/src/OAuth2.php:648
Stack trace:
#0 /Users/krislec/Desktop/vendor/google/apiclient/src/Google/Client.php(340): Google\Auth\OAuth2->buildFullAuthorizationUri(Array)
#1 /Users/krislec/Desktop/quickstart.php(36): Google_Client->createAuthUrl()
#2 /Users/krislec/Desktop/quickstart.php(75): getClient()
#3 {main}
thrown in /Users/krislec/Desktop/vendor/google/auth/src/OAuth2.php on line 648
Fatal error: Uncaught InvalidArgumentException: missing the required redirect URI in /Users/krislec/Desktop/vendor/google/auth/src/OAuth2.php:648
Stack trace:
#0 /Users/krislec/Desktop/vendor/google/apiclient/src/Google/Client.php(340): Google\Auth\OAuth2->buildFullAuthorizationUri(Array)
#1 /Users/krislec/Desktop/quickstart.php(36): Google_Client->createAuthUrl()
#2 /Users/krislec/Desktop/quickstart.php(75): getClient()
#3 {main}
thrown in /Users/krislec/Desktop/vendor/google/auth/src/OAuth2.php on line 648

Refresh Token Expiration time Google calendar

Can any one please tell me the expiration time for Refresh token generation through OAuth2. Actually it usually returns 2 parameter access token and refresh token. We used refresh token in order to generate a new access toke if access token get expired. But Google Calendar Version3, i am using refresh token in order to call the calendar API. But here i am getting a problem that token get expires. So can any one please suggest me what can i do when token get expires. According to me there is no expiration time for refresh token. Kindly check the code below for creation of calendar service using refresh token :-
private CalendarService CreateService(string token)
{
KeyValuePair<string, string> credentials = Common.Get3LOCredentials();
var provider = new NativeApplicationClient(GoogleAuthenticationServer.Description);
provider.ClientIdentifier = credentials.Key;
provider.ClientSecret = credentials.Value;
var auth = new Google.Apis.Authentication.OAuth2.OAuth2Authenticator<NativeApplicationClient>(provider, (p) => GetAuthorization(provider, token));
CalendarService service = new CalendarService(new BaseClientService.Initializer()
{
Authenticator = auth,
ApiKey = ConfigurationManager.AppSettings["APIkey"].ToString(),
GZipEnabled = false
});
return service;
}
private static IAuthorizationState GetAuthorization(NativeApplicationClient arg, String Refreshtoken)
{
IAuthorizationState state = new AuthorizationState(new[] { CalendarService.Scopes.Calendar.GetStringValue() });
state.Callback = new Uri(NativeApplicationClient.OutOfBandCallbackUrl);
state.RefreshToken = Refreshtoken;
return state;
}
Refresh Tokens do not expire but they can be revoked. You, the token handler can revoke the token yourself programatically or the end user can revoke the token in their account settings. Make sure you are properly using the refresh token to get a new access token and you're not attempting to use an access token that has expired.
Can you edit your question to show the exact error you are getting?

'Google_Exception' with message 'Cant add services after having authenticated'

I am working on a WP plugin with Google Analytics, using Oauth 2.0.
All of my authentication & data pulls work fine, with the exception of this one issue: the first time I get a new Google authorization code (ex: "4/-xbSbg...." ) & authenticate, then try to call a new Google_AnalyticsService() object, it tosses back the error:
'Google_Exception' with message 'Cant add services after having authenticated'
This is on line 109: http://code.google.com/p/google-api-php-client/source/browse/trunk/src/apiClient.php?r=258
Once I refresh the page that calls this code, it works fine - ie, the first branch of the check_login() is ok, but the authentication call is not working correctly.
You see that the code seems to be complaining because I DID authenticate first, and the message says I shouldn't do that. The comment & code really have me confused what my issue is (login code not very clean yet, I realize).
IMPORTANT NOTE: I am using the Google Auth for Installed Apps, and so we are asking for an auth code from user, and using that to obtain the auth token.
get_option(), set_option() & update_option() are WP native functions that are not a part of the problem
Here is my code:
class GoogleAnalyticsStats
{
var $client = false;
function GoogleAnalyticsStats()
{
$this->client = new Google_Client();
$this->client->setClientId(GOOGLE_ANALYTICATOR_CLIENTID);
$this->client->setClientSecret(GOOGLE_ANALYTICATOR_CLIENTSECRET);
$this->client->setRedirectUri(GOOGLE_ANALYTICATOR_REDIRECT);
$this->client->setScopes(array(GOOGLE_ANALYTICATOR_SCOPE));
// Magic. Returns objects from the Analytics Service instead of associative arrays.
$this->client->setUseObjects(true);
}
function checkLogin()
{
$ga_google_authtoken = get_option('ga_google_authtoken');
if (!empty($ga_google_authtoken))
{
$this->client->setAccessToken($ga_google_authtoken);
}
else
{
$authCode = get_option('ga_google_token');
if (empty($authCode)) return false;
$accessToken = $this->client->authenticate($authCode);
$this->client->setAccessToken($accessToken);
update_option('ga_google_authtoken', $accessToken);
}
return true;
}
function getSingleProfile()
{
$analytics = new Google_AnalyticsService($this->client);
}
}
You will need to move $analytics = new Google_AnalyticsService($this->client); inside function GoogleAnalyticsStats(), and preferably turn $analytics into a member variable.
class GoogleAnalyticsStats
{
var $client = false;
var $analytics = false;
function GoogleAnalyticsStats()
{
$this->client = new Google_Client();
$this->client->setClientId(GOOGLE_ANALYTICATOR_CLIENTID);
$this->client->setClientSecret(GOOGLE_ANALYTICATOR_CLIENTSECRET);
$this->client->setRedirectUri(GOOGLE_ANALYTICATOR_REDIRECT);
$this->client->setScopes(array(GOOGLE_ANALYTICATOR_SCOPE));
// Magic. Returns objects from the Analytics Service instead of associative arrays.
$this->client->setUseObjects(true);
$this->analytics = new Google_AnalyticsService($this->client);
}
...
Now, you can make calls to the analytics API from within getSingleProfile.

Resources