Google OAuth 2.0 refresh token for web application with public access

12,876

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);
Share:
12,876
Moose
Author by

Moose

3X Certified M1 Developer, Magento Solution Specialist

Updated on June 04, 2022

Comments

  • Moose
    Moose almost 2 years

    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.