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

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

使用PHP Quickstart代码,发现一个问题:需要刷新token时,代码returns出现如下错误:

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

我修改了 getClient() 函数,如下所示:

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

第一次认证后(使用在getClient()函数中创建的link),当用户登陆到REDIRECT_URL时,会执行一个callbackAuth()函数:

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

我尝试应用其他相关的解决方案,但没有结果。为什么会出现这个错误?

感谢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.