Search code examples
phpgoogle-apiaccess-tokengoogle-api-php-client

Google API Fatal error: Uncaught LogicException: refresh token must be passed in or set as part of setAccessToken


Working with the PHP Quickstart code, I found a problem: when the token needs to be refreshed, the code returns the following error:

Fatal error: Uncaught LogicException: refresh token must be passed in or set as part of setAccessToken in /app/vendor/google/apiclient/src/Google/Client.php:258

Stack trace:

#0 /app/gmail.php(32): Google_Client->fetchAccessTokenWithRefreshToken(NULL)

#1 /app/test.php(14): getClient()

#2 {main} thrown in /app/vendor/google/apiclient/src/Google/Client.php on line 258

I modified the getClient() function like this:

function getClient() {
    $client = new Google_Client();
    $client->setApplicationName(APPLICATION_NAME);
    $client->setScopes(SCOPES);
    $client->setAuthConfig(CLIENT_SECRET_PATH);
    $client->setRedirectUri(REDIRECT_URL);
    $client->setAccessType('offline');
    $client->setApprovalPrompt('force');

    // 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();
        return printf("<a href='%s' target='_blank'>auth</a><br />", $authUrl);
    }
    $client->setAccessToken($accessToken);

    // Refresh the token if it's expired.
    // ERROR HERE !!
    if ($client->isAccessTokenExpired()) {
        $client->fetchAccessTokenWithRefreshToken($client->getRefreshToken());
        file_put_contents($credentialsPath, json_encode($client->getAccessToken()));
    }
    return $client;
}

After the first authentication (using the link created in the getClient() function), when the user lands to REDIRECT_URL, a callbackAuth() function is executed:

function callbackAuth() {
    $client = new Google_Client();
    $client->setApplicationName(APPLICATION_NAME);
    $client->setScopes(SCOPES);
    $client->setAuthConfig(CLIENT_SECRET_PATH);
    $client->setRedirectUri(REDIRECT_URL);
    $client->setAccessType('offline');
    $client->setApprovalPrompt('force');

    // Load previously authorized credentials from a file.
    $credentialsPath = expandHomeDirectory(CREDENTIALS_PATH);

    // Request authorization from the user.
    $authCode = trim($_GET['code']);

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

    return $client;
}

I tried to apply the solutions of other related stackoverflow question, but without results. Why does this error ocurr?


Solution

  • Thanks to Alex Blex I was able to notice that the first time I receive the token has the refresh_token but after the first request, the refresh_token is not stored. The solution is the following (from this answer):

    // Refresh the token if it's expired.
    if ($client->isAccessTokenExpired()) {
        $client->fetchAccessTokenWithRefreshToken($client->getRefreshToken());
        $newAccessToken = $client->getAccessToken();
        $accessToken = array_merge($accessToken, $newAccessToken);
        file_put_contents($credentialsPath, json_encode($accessToken));
    }
    

    The refresh_token is only returned on the first request. When you refresh the access token a second time it returns everything except the refresh_token and the file_put_contents removes the refresh_token when this happens the second time.

    Modifying the code as following will merge in the original access token with the new one. This way you will be able to preserve your refresh_token for future requests.