9

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.

Community
  • 1
  • 1
Moose
  • 610
  • 1
  • 14
  • 28

1 Answers1

13

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);
Hafez Divandari
  • 8,381
  • 4
  • 46
  • 63
  • Thanks very much! Works perfectly. I requested an edit on the post to remove the bit where you `json_decode` the $credentialsJson on the 2nd API call as `OAuth2.php` `setAccessToken` line 179 does this for you, and throws a fatal error if you do it beforehand. – Moose Sep 03 '14 at 06:50
  • Yes you're right, I also removed line 14 of first script which was wrong. I wanted to `json_encode($credentials)` but `authenticate` does this for us. – Hafez Divandari Sep 03 '14 at 13:23
  • `$access_token = $client->getAccessToken();` and `$credentials = $client->authenticate($_GET['code']);` are same. My `$credentials` is not contain refresh_token value. It only contains access_token, token_type, expires_in, created – mcan Apr 26 '15 at 13:15
  • 2
    @Mutlu as I said, you have to set `$client->setAccessType('offline');` and `$client->setApprovalPrompt('force');` to force your `$credentials` contains refresh_token. – Hafez Divandari Apr 27 '15 at 14:42
  • google docs say you need to set the `approvalPrompt` to `consent` to make sure the refreshtoken is granted https://developers.google.com/identity/protocols/oauth2/web-server#creatingclient – Muhammad Omer Aslam Oct 22 '22 at 03:02
  • also when we say save credentials does it mean saving all the info reflected in the creditials array as json_encode? and passing it when we require to call any service for the second part ? or we just need to save the refresh token returned in the credentials array ? – Muhammad Omer Aslam Oct 22 '22 at 03:59