Google OAuth 2.0 refresh token for web application with public access

Moose picture Moose · Aug 27, 2014 · Viewed 10k times · Source

I'm getting the following 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 have a web application which only my server will be accessing, the initial auth works fine, but after an hour, the aforementioned error message pops up. My script looks like this:

require_once 'Google/Client.php';     
require_once 'Google/Service/Analytics.php';       
session_start();

$client = new Google_Client();
$client->setApplicationName("Client_Library_Examples");
$client->setDeveloperKey("{MY_API_KEY}");
$client->setClientId('{MY_CLIENT_ID}.apps.googleusercontent.com');
$client->setClientSecret('{MY_KEY}');
$client->setRedirectUri('{MY_REDIRECT_URI}');
$client->setScopes(array('https://www.googleapis.com/auth/gmail.readonly'));

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));
}

if (!$client->getAccessToken() && !isset($_SESSION['token'])) {
    $authUrl = $client->createAuthUrl();
    print "<a class='login' href='$authUrl'>Connect Me!</a>";
}

How can I set up a refresh token for this?

Edit 1: I've tried doing this with a Service account as well. I followed the documentation available on GitHub:

https://github.com/google/google-api-php-client/blob/master/examples/service-account.php

My script looks like this:

session_start();
include_once "templates/base.php";
require_once 'Google/Client.php';
require_once 'Google/Service/Gmail.php';
$client_id = '{MY_CLIENT_ID}.apps.googleusercontent.com'; 
$service_account_name = '{MY_EMAIL_ADDRESS}@developer.gserviceaccount.com ';
$key_file_location = 'Google/{MY_KEY_FILE}.p12';
echo pageHeader("Service Account Access");
    if ($client_id == ''
|| !strlen($service_account_name)
|| !strlen($key_file_location)) {
  echo missingServiceAccountDetailsWarning();
}
$client = new Google_Client();
$client->setApplicationName("Client_Library_Examples");
$service = new Google_Service_Gmail($client);
if (isset($_SESSION['service_token'])) {
  $client->setAccessToken($_SESSION['service_token']);
}
$key = file_get_contents($key_file_location);
$cred = new Google_Auth_AssertionCredentials(
    $service_account_name,
    array('https://www.googleapis.com/auth/gmail.readonly'),
    $key
);
$client->setAssertionCredentials($cred);
if($client->getAuth()->isAccessTokenExpired()) {
  $client->getAuth()->refreshTokenWithAssertion($cred);
}

This returns the following message:

Error refreshing the OAuth2 token, message: '{ "error" : "invalid_grant" }''

After tracking this code to it's source, which can be found in Google/Auth/OAuth2.php

The method in question, refreshTokenRequest:

private function refreshTokenRequest($params)
{
    $http = new Google_Http_Request(
    self::OAUTH2_TOKEN_URI,
    'POST',
    array(),
    $params
    );
    $http->disableGzip();
    $request = $this->client->getIo()->makeRequest($http);

    $code = $request->getResponseHttpCode();
    $body = $request->getResponseBody();
    if (200 == $code) {
      $token = json_decode($body, true);
      if ($token == null) {
        throw new Google_Auth_Exception("Could not json decode the access token");
      }

      if (! isset($token['access_token']) || ! isset($token['expires_in']))     {
        throw new Google_Auth_Exception("Invalid token format");
      }

      if (isset($token['id_token'])) {
        $this->token['id_token'] = $token['id_token'];
      }
      $this->token['access_token'] = $token['access_token'];
      $this->token['expires_in'] = $token['expires_in'];
      $this->token['created'] = time();
    } else {
      throw new Google_Auth_Exception("Error refreshing the OAuth2 token, message: '$body'", $code);
    }
  }

Which means that the $code variable is NULL. I found this post on SO:

Error refreshing the OAuth2 token { “error” : “invalid_grant” }

And can see that there is still no prefered solution. This is driving me nuts. There is very little to no documentation available on this and if anybody has a solution, I'm sure I'm not the only one looking for it.

Answer

Hafez Divandari picture Hafez Divandari · Sep 2, 2014

Each access_token expires after a few seconds and need to be refreshed by refresh_token, "Offline access" is what you are looking for. Here you can follow the documentation:

https://developers.google.com/accounts/docs/OAuth2WebServer#offline

To obtain a refresh_token you have to run this code only one time:

require_once 'Google/Client.php';

$client = new Google_Client();
$client->setClientId('{MY_CLIENT_ID}.apps.googleusercontent.com');
$client->setClientSecret('{MY_KEY}');
$client->setRedirectUri('{MY_REDIRECT_URI}');
//next two line added to obtain refresh_token
$client->setAccessType('offline');
$client->setApprovalPrompt('force');
$client->setScopes(array('https://www.googleapis.com/auth/gmail.readonly'));

if (isset($_GET['code'])) {
    $credentials = $client->authenticate($_GET['code']);  

    /*TODO: Store $credentials somewhere secure */

} else {
    $authUrl = $client->createAuthUrl();
    print "<a class='login' href='$authUrl'>Connect Me!</a>";
}

$credentials includes an access token and a refresh token. You have to store this to obtain new access tokens at any time. So when you wanted to make a call to api:

require_once 'Google/Client.php';
require_once 'Google/Service/Gmail.php';

/*TODO: get stored $credentials */

$client = new Google_Client();
$client->setClientId('{MY_CLIENT_ID}.apps.googleusercontent.com');
$client->setRedirectUri('{MY_REDIRECT_URI}');
$client->setClientSecret('{MY_KEY}');
$client->setScopes(array('https://www.googleapis.com/auth/gmail.readonly'));
$client->setAccessToken($credentials);

$service = new Google_Service_Gmail($client);