Использование Refre sh Token в Gmail API. Отслеживание старых ключей - PullRequest
2 голосов
/ 21 апреля 2020

TL; DR

Проблема здесь в том, что я не могу выполнить fetchAccessTokenWithRefreshToken() без предварительного выполнения setAccessToken(), а для выполнения setAccessToken() мне нужно знать предыдущий токен доступа.

Если я пытаюсь fetchAccessTokenWithRefreshToken() без первого вызова setAccessToken(), я получаю «LogicException: refre sh токен должен быть передан или установлен как часть setAccessToken»

Если я пытаюсь setAccessToken() только со значением refresh_token он также завершается ошибкой с Invalid token format

Единственный способ, которым он работает, - это предоставление ПОЛНОГО действительного пакета токенов аутентификации, а не только токена refre sh.

Я использую этот скрипт, чтобы получить первый аутентификатор и сгенерировать токен refre sh:

<?php
require __DIR__ . '/vendor/autoload.php';

$client = new Google_Client();

$client->setApplicationName('Gmail API Generate Refresh Token');
$client->setScopes(Google_Service_Gmail::GMAIL_MODIFY);
$client->setAuthConfig('credentials.json');
$client->setAccessType('offline');
$client->setPrompt('select_account consent');

// Request authorization from the user.
$authUrl = $client->createAuthUrl();
printf("Open the following link in your browser:\n%s\n", $authUrl);
print 'Enter verification code: ';
$authCode = trim(fgets(STDIN));

// Exchange authorization code for an access token.
$accessToken = $client->fetchAccessTokenWithAuthCode($authCode);
$client->setAccessToken($accessToken);
if (array_key_exists('error', $accessToken)) {
    throw new Exception(join(', ', $accessToken));
}
print "\n\nBelow is the refresh token.\n\n";
print "Use this token with an authenticated Google_Client object to refresh auth tokens.\n";
print "This string MUST be saved otherwise you will need user approval again\n\n";
print $client->getRefreshToken();

file_put_contents('token.json', json_encode($client->getAccessToken()));
print "\n\nThe first auth token bundle has been saved to token.json\n";

Обзор

  • У меня есть полностью работающее серверное приложение, которое используется для взаимодействия с почтовым ящиком Gmail одного пользователя.
    • У меня есть кредиты API и токен refre sh, сохраненный в постоянном безопасном хранилище.
    • Приложение может успешно взаимодействовать с Gmail до истечения срока действия токена доступа.

В целом цель состоит в том, чтобы иметь возможность взаимодействовать с API Gmail, когда приложение имеет ТОЛЬКО следующее: действительный идентификатор клиента / секретный ключ и refresh_token. Я обнаружил, что мне нужно отслеживать токены аутентификации, а также токен refre sh.

Вот проблема, с которой я сталкиваюсь:

  • По истечении срока действия токена доступа я не могу сгенерировать новый, используя только мои кредиты API и refre sh маркер.
  • Я получаю сообщение об ошибке invalid_grant, если я пытаюсь вызвать fetchAccessTokenWithRefreshToken(), используя только строку токена refre sh в качестве параметра.
  • Единственный способ, которым я могу получить его, чтобы дать мне новый токен, это предоставить refre sh токен И текущую информацию о токене доступа!
    • Мне нужен не только сам оригинальный токен доступа, но и значения created и expires_in.

Я должен передать ему полный массив информации: [access_token, expires_in, created, refresh_token] в противном случае он просто не будет работать!

Очевидно, что я делаю что-то здесь не так, я думаю, все, что мне нужно, это токен refre sh для генерации токенов доступа по мере необходимости.


[PHP] Вот некоторые фрагменты:

(обратите внимание, что это только dev, я не планирую жестко кодировать секреты в коде)

Вот что я бы «ожидал» для работы (это НЕ работает): Ошибка здесь invalid_grant

    $client = new Google_Client();
    $client->setApplicationName('Gmail API PHP Quickstart');
    $client->setScopes(Google_Service_Gmail::GMAIL_MODIFY);
    $client->setAuthConfig('credentials.json');
    $client->setAccessType('offline');
    $client->setPrompt('select_account consent');

    // This is the REFRESH token
    $token = '1\/\/0dKcfaketokentokentokenfaketoken-L9IriuoNveLzVQ1w4-lPfakeEPn1R1NjcOK2ISE--O1PO1yEtokenr87E';

    // var_dump just for sanity to ensure this returns true
    var_dump($client->isAccessTokenExpired());

    if ($client->isAccessTokenExpired()) {
      var_dump($client->fetchAccessTokenWithRefreshToken($token));
    }

ЭТО РАБОТАЕТ, потому что я ' m загружает полный массив $token (или это потому, что я предварительно предоставил setAccessToken () заранее.:

    $token = array();
    $token['access_token'] = '<<SCRUBBED_CURRENT_ACCESS_TOKEN>>>';
    $token['expires_in'] = 3599;
    $token['refresh_token'] = '<<SCRUBBED_REFRESH_TOKEN>>';
    $token['created'] = 1587447211;

    // If I leave out ANY of the values above, the token refresh does not work! 

    // omitted some Gmail client configuration and setup. 

    $this->client->setAccessToken($token);

    if ($this->client->isAccessTokenExpired()) {
      $this->accessToken =  $this->client->fetchAccessTokenWithRefreshToken($token);
    }
    else {
      $this->accessToken = $this->client->getAccessToken();
    }
    $this->service = new Google_Service_Gmail($this->client);

Подсказка:

Я заметил на https://github.com/googleapis/google-api-php-client/blob/1fdfe942f9aaf3064e621834a5e3047fccb3a6da/src/Google/Client.php#L275 fetchAccessTokenWithRefreshToken() откатится к существующему токену, это объясняет, почему это будет работать, когда я предварительно настрою существующий токен. эта загадка, кажется, разгадана. Не объясняет, почему он все еще не работает без предустановленного токена.

Я бы ожидал, что он просто будет работать так:

    // Omitted initial Gmail client setup

    if ($this->client->isAccessTokenExpired()) {
      $this->accessToken =  $this->client->fetchAccessTokenWithRefreshToken('<MY_REFRESH_TOKEN');
    }
    else {
      $this->accessToken = $this->client->getAccessToken();
    }
    $this->service = new Google_Service_Gmail($this->client);

Это тоже не работает (подтвердил, что значение getRefreshToken() является хорошим):

$this->client->fetchAccessTokenWithRefreshToken($this->client->getRefreshToken());

1 Ответ

4 голосов
/ 21 апреля 2020

invalid_grant

Означает, что используемый вами токен недействителен или устарел, или вы пытаетесь использовать действительный refre sh токен с идентификатором клиента и секретом, которые не были используется для его создания. В этом случае вы отправляете объект и должны отправлять туда только действительный токен refre sh, так как значение, которое вы отправляете методу, неверно.

Этот метод fetchAccessTokenWithRefreshToken принимает токен refre sh не объект. Просто передайте ему refre sh токен.

Пример: ClientTest.php # L485

создание клиента

$client = new Google_Client();
$client->setAccessType("offline");        // offline access.  Will result in a refresh token
$client->setIncludeGrantedScopes(true);   // incremental auth
$client->setAuthConfig(__DIR__ . '/client_secrets.json');
$client->addScope([YOUR SCOPES HERE]);
$client->setRedirectUri(getRedirectUri());  

проверка на срок действия и refre sh если необходимо.

if ($client->isAccessTokenExpired()) {
        $client->fetchAccessTokenWithRefreshToken($client->getRefreshToken());      
    }

Примечание

Если вы получаете токен refre sh, сохраненный и созданный другим сценарием, что система, обновляющая токен, должна использовать тот же самый идентификатор клиента и секрет клиента IE (client_secrets. json), чтобы иметь возможность использовать его для обновления sh доступа. Это не может быть просто еще один в том же проекте, это должны быть те же секреты.

также см. Oauth2Authencation. php

Полный пример сохранения токена в папку

oauth2callback. php

require_once __DIR__ . '/vendor/autoload.php';
require_once __DIR__ . '/Oauth2Authentication.php';

// Start a session to persist credentials.
session_start();

// Handle authorization flow from the server.
if (! isset($_GET['code'])) {
    $client = buildClient();
    $auth_url = $client->createAuthUrl();
    header('Location: ' . filter_var($auth_url, FILTER_SANITIZE_URL));
} else {
    $client = buildClient();
    $client->authenticate($_GET['code']); // Exchange the authencation code for a refresh token and access token.
    // Add access token and refresh token to seession.
    $_SESSION['access_token'] = $client->getAccessToken();
    $_SESSION['refresh_token'] = $client->getRefreshToken();    
    //Redirect back to main script
    $redirect_uri = str_replace("oauth2callback.php",$_SESSION['mainScript'],$client->getRedirectUri());    
    header('Location: ' . filter_var($redirect_uri, FILTER_SANITIZE_URL));
}

Oauth2Authentication. php

require_once __DIR__ . '/vendor/autoload.php';
/**
 * Gets the Google client refreshing auth if needed.
 * Documentation: https://developers.google.com/identity/protocols/OAuth2
 * Initializes a client object.
 * @return A google client object.
 */
function getGoogleClient() {
    $client = getOauth2Client();

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

/**
 * Builds the Google client object.
 * Documentation: https://developers.google.com/identity/protocols/OAuth2
 * Scopes will need to be changed depending upon the API's being accessed.
 * Example:  array(Google_Service_Analytics::ANALYTICS_READONLY, Google_Service_Analytics::ANALYTICS)
 * List of Google Scopes: https://developers.google.com/identity/protocols/googlescopes
 * @return A google client object.
 */
function buildClient(){

    $client = new Google_Client();
    $client->setAccessType("offline");        // offline access.  Will result in a refresh token
    $client->setIncludeGrantedScopes(true);   // incremental auth
    $client->setAuthConfig(__DIR__ . '/client_secrets.json');
    $client->addScope([YOUR SCOPES HERE]);
    $client->setRedirectUri(getRedirectUri());  
    return $client;
}

/**
 * Builds the redirect uri.
 * Documentation: https://developers.google.com/api-client-library/python/auth/installed-app#choosingredirecturi
 * Hostname and current server path are needed to redirect to oauth2callback.php
 * @return A redirect uri.
 */
function getRedirectUri(){

    //Building Redirect URI
    $url = $_SERVER['REQUEST_URI'];                    //returns the current URL
    if(strrpos($url, '?') > 0)
        $url = substr($url, 0, strrpos($url, '?') );  // Removing any parameters.
    $folder = substr($url, 0, strrpos($url, '/') );   // Removeing current file.
    return (isset($_SERVER['HTTPS']) ? "https" : "http") . '://' . $_SERVER['HTTP_HOST'] . $folder. '/oauth2callback.php';
}


/**
 * Authenticating to Google using Oauth2
 * Documentation:  https://developers.google.com/identity/protocols/OAuth2
 * Returns a Google client with refresh token and access tokens set. 
 *  If not authencated then we will redirect to request authencation.
 * @return A google client object.
 */
function getOauth2Client() {
    try {

        $client = buildClient();

        // Set the refresh token on the client. 
        if (isset($_SESSION['refresh_token']) && $_SESSION['refresh_token']) {
            $client->refreshToken($_SESSION['refresh_token']);
        }

        // If the user has already authorized this app then get an access token
        // else redirect to ask the user to authorize access to Google Analytics.
        if (isset($_SESSION['access_token']) && $_SESSION['access_token']) {

            // Set the access token on the client.
            $client->setAccessToken($_SESSION['access_token']);                 

            // Refresh the access token if it's expired.
            if ($client->isAccessTokenExpired()) {              
                $client->fetchAccessTokenWithRefreshToken($client->getRefreshToken());
                $client->setAccessToken($client->getAccessToken()); 
                $_SESSION['access_token'] = $client->getAccessToken();              
            }           
            return $client; 
        } else {
            // We do not have access request access.
            header('Location: ' . filter_var( $client->getRedirectUri(), FILTER_SANITIZE_URL));
        }
    } catch (Exception $e) {
        print "An error occurred: " . $e->getMessage();
    }
}

Примечание: refre sh Токен гарантированно вернется только при первом вызове, когда пользователь впервые согласится на ваш доступ к его данным. Google предполагает, что вы сохранили токен refre sh, поэтому он не отправляет новый. Вот почему только маркер доступа снова сохраняется после того, как система автоматически обновит sh токен доступа, используя сохраненный токен refre sh.

...