Не получен токен обновления Google OAuth

Я хочу получить токен доступа от Google. Google API говорит, что для получения токена доступа отправьте код и другие параметры на страницу создания токена, и ответом будет объект JSON, подобный следующему:

{
"access_token" : "ya29.AHES6ZTtm7SuokEB-RGtbBty9IIlNiP9-eNMMQKtXdMP3sfjL1Fc",
"token_type" : "Bearer",
"expires_in" : 3600,
"refresh_token" : "1/HKSmLFXzqP0leUihZp2xUt3-5wkU7Gmu2Os_eBnzw74"
}

Однако я не получаю токен обновления. Ответ в моем случае:

{
 "access_token" : "ya29.sddsdsdsdsds_h9v_nF0IR7XcwDK8XFB2EbvtxmgvB-4oZ8oU",
"token_type" : "Bearer",
"expires_in" : 3600
}

20 ответов

Решение

refresh_token предоставляется только при первой авторизации от пользователя. Последующие авторизации, например, которые вы делаете при тестировании интеграции OAuth2, не возвращают refresh_token снова.:)

  1. Перейдите на страницу, на которой показаны приложения с доступом к вашей учетной записи: https://myaccount.google.com/u/0/permissions.
  2. В меню Сторонние приложения выберите свое приложение.
  3. Нажмите Удалить доступ, а затем нажмите ОК, чтобы подтвердить
  4. Следующий запрос OAuth2 вернет refresh_token,

Кроме того, вы можете добавить параметр запроса prompt=consent к перенаправлению OAuth (см. страницу Google OAuth 2.0 для приложений веб-сервера).

Это побудит пользователя снова авторизовать приложение и всегда вернет refresh_token,

Чтобы получить токен обновления, вы должны добавить оба approval_prompt=force а также access_type="offline"Если вы используете Java-клиент, предоставленный Google, он будет выглядеть так:

GoogleAuthorizationCodeFlow flow = new GoogleAuthorizationCodeFlow.Builder(
            HTTP_TRANSPORT, JSON_FACTORY, getClientSecrets(), scopes)
            .build();

AuthorizationCodeRequestUrl authorizationUrl =
            flow.newAuthorizationUrl().setRedirectUri(callBackUrl)
                    .setApprovalPrompt("force")
                    .setAccessType("offline");

Я хотел бы добавить немного больше информации по этому вопросу для тех разочарованных душ, которые сталкиваются с этой проблемой. Ключ к получению токена обновления для автономного приложения — убедиться, что вы представляете экран согласия . refresh_tokenвозвращается только сразу после того, как пользователь предоставит авторизацию, нажав «Разрешить».

Эта проблема возникла у меня (и я подозреваю, что у многих других) после того, как я провел некоторое тестирование в среде разработки и, следовательно, уже авторизовал свое приложение в данной учетной записи. Затем я перешел к производству и попытался снова пройти аутентификацию, используя учетную запись, которая уже была авторизована. В этом случае экран согласия не появится снова, и API не вернет новый токен обновления . Чтобы это сработало, вы должны принудительно снова отобразить экран согласия одним из следующих способов:

      prompt=consent

или же

      approval_prompt=force

Любой из них будет работать, но вы не должны использовать оба. С 2021 года я бы рекомендовал использовать prompt=consentпоскольку он заменяет старый параметр approval_promptи в некоторых версиях API последний был фактически сломан (https://github.com/googleapis/oauth2client/issues/453). Также, promptэто список, разделенный пробелами, поэтому вы можете установить его как prompt=select_account%20consentесли вы хотите оба.

Конечно, вам также нужно:

      access_type=offline

Дополнительное чтение:

Я искал долгую ночь, и это делает свое дело:

Модифицированный user-example.php из admin-sdk

$client->setAccessType('offline');
$client->setApprovalPrompt('force');
$authUrl = $client->createAuthUrl();
echo "<a class='login' href='" . $authUrl . "'>Connect Me!</a>";

затем вы получите код по URL-адресу перенаправления и аутентификацию с кодом и получите токен обновления

$client()->authenticate($_GET['code']);
echo $client()->getRefreshToken();

Вы должны хранить его сейчас;)

Когда ваш accesskey истекает, просто сделайте

$client->refreshToken($theRefreshTokenYouHadStored);

Это вызвало у меня некоторую путаницу, поэтому я решил поделиться тем, чему научился на своем нелегком пути:

Когда вы запрашиваете доступ с помощью access_type=offline а также approval_prompt=force Параметры вы должны получить как токен доступа, так и токен обновления. Срок действия токена истекает вскоре после его получения, и вам нужно будет обновить его.

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

Я думаю, что некоторые другие ответы предполагают, что вы по какой-то причине захотели получить себе новый токенобновления, и предположили, что вы повторно авторизуете пользователя, но на самом деле вам это не нужно, так как имеющийся у вас токен обновления будет работать до отозвано пользователем.

Ответ Рича Саттона, наконец, сработал для меня, после того как я понял, что добавление access_type=offline выполняется по запросу клиентского интерфейса для кода авторизации, а не по внутреннему запросу, который обменивает этот код на access_token. Я добавил комментарий к его ответу и эту ссылку в Google для получения дополнительной информации об обновлении токенов.

PS Если вы используете Satellizer, вот как добавить эту опцию в $ authProvider.google в AngularJS.

Для того, чтобы получить refresh_token вам нужно включить access_type=offline в URL-адресе запроса OAuth. Когда пользователь впервые авторизуется, вы получите не ноль refresh_token а также access_token это истекает.

Если вы столкнулись с ситуацией, когда пользователь может повторно аутентифицировать учетную запись, для которой у вас уже есть токен аутентификации (как упомянуто выше в @SsjCosty), вам необходимо получить информацию от Google, для какой учетной записи предназначен токен. Для этого добавьте profile в твои возможности. С использованием OAuth2 Ruby gem ваш окончательный запрос может выглядеть примерно так:

client = OAuth2::Client.new(
  ENV["GOOGLE_CLIENT_ID"],
  ENV["GOOGLE_CLIENT_SECRET"],
  authorize_url: "https://accounts.google.com/o/oauth2/auth",
  token_url: "https://accounts.google.com/o/oauth2/token"
)

# Configure authorization url
client.authorize_url(
  scope: "https://www.googleapis.com/auth/analytics.readonly profile",
  redirect_uri: callback_url,
  access_type: "offline",
  prompt: "select_account"
)

Обратите внимание, что в области есть две записи, разделенные пробелами, одна для доступа только для чтения к Google Analytics, а другая просто profile, который является стандартом OpenID Connect.

Это приведет к тому, что Google предоставит дополнительный атрибут с именем id_token в get_token ответ. Чтобы получить информацию из id_token, просмотрите эту страницу в Google docs. Существует несколько библиотек, предоставленных Google, которые будут проверять и "декодировать" это для вас (я использовал гем Ruby google-id-token). После того, как вы его разобрали, sub Параметр фактически является уникальным идентификатором учетной записи Google.

Стоит отметить, что если вы измените область действия, вы снова получите токен обновления для пользователей, которые уже прошли проверку подлинности в исходной области действия. Это полезно, если, скажем, у вас уже есть группа пользователей, и вы не хотите, чтобы они все отключили приложение в Google.

Да, и последнее замечание: вам не нужно prompt=select_account, но это полезно, если у вас есть ситуация, когда ваши пользователи могут захотеть пройти аутентификацию с использованием нескольких учетных записей Google (т. е. вы не используете это для входа в систему / аутентификации).

Я использую клиент nodejs для доступа к личным данным

Решением было добавить свойство promp со значением согласия в объект настроек в функции oAuth2Client.generateAuthUrl. Вот мой код:

const getNewToken = (oAuth2Client, callback) => {
    const authUrl = oAuth2Client.generateAuthUrl({
        access_type: 'offline',
        prompt: 'consent',
        scope: SCOPES,
    })
    console.log('Authorize this app by visiting this url:', authUrl)
    const rl = readline.createInterface({
        input: process.stdin,
        output: process.stdout,
    })
    rl.question('Enter the code from that page here: ', (code) => {
        rl.close()
        oAuth2Client.getToken(code, (err, token) => {
            if (err) return console.error('Error while trying to retrieve access token', err)
            oAuth2Client.setCredentials(token)
            // Store the token to disk for later program executions
            fs.writeFile(TOKEN_PATH, JSON.stringify(token), (err) => {
                if (err) return console.error(err)
                console.log('Token stored to', TOKEN_PATH)
            })
            callback(oAuth2Client)
        })
    })
}

Вы можете использовать онлайн-экстрактор параметров, чтобы получить код для генерации вашего токена:

Онлайн-экстрактор параметров

Вот полный код из официальных документов Google:

https://developers.google.com/sheets/api/quickstart/nodejs

Надеюсь информация будет полезной

1. Как получить "refresh_token"?

Решение: при генерации authURL следует использовать опцию access_type = 'offline'. источник: Использование OAuth 2.0 для приложений веб-сервера

2. Но даже с 'access_type=offline' я не получаю 'refresh_token'?

Решение: обратите внимание, что вы получите его только при первом запросе, поэтому, если вы где-то храните его и есть возможность перезаписать это в вашем коде при получении нового access_token после истечения предыдущего срока, убедитесь, что не перезаписываете это значение.

Из Google Auth Doc: (это значение = access_type)

Это значение предписывает серверу авторизации Google вернуть токен обновления и токен доступа при первом обмене приложением кода авторизации на токены.

Если вам снова понадобится "refresh_token", вам необходимо удалить доступ для своего приложения, выполнив шаги, написанные в ответе Rich Sutton.

#Март - 2023

      Short answer -->

access_type: 'offline',
prompt: 'consent',

pass these in  oAuth2Client.generateAuthUrl({})
  • Чтобы получить токен обновления с помощью почтальона, вот пример конфигурации

  • Ожидаемый ответ

Установка этого параметра приведет к тому, что токен обновления будет отправляться каждый раз:

$client->setApprovalPrompt('force');

пример приведен ниже (php):

$client = new Google_Client();
$client->setClientId($client_id);
$client->setClientSecret($client_secret);
$client->setRedirectUri($redirect_uri);
$client->addScope("email");
$client->addScope("profile"); 
$client->setAccessType('offline');
$client->setApprovalPrompt('force');

Чтобы каждый раз получать новый refresh_token при аутентификации, тип учетных данных OAuth 2.0, созданных на панели мониторинга, должен быть "Другой". Также, как упомянуто выше, опция access_type='offline' должна использоваться при генерации authURL.

При использовании учетных данных с типом "веб-приложение" никакая комбинация переменных prompt / Approve_prompt работать не будет - вы все равно получите refresh_token только при первом запросе.

Для меня я пытался CalendarSampleServlet предоставлено Google. Через 1 час тайм- аут access_key и происходит перенаправление на страницу 401. Я перепробовал все вышеперечисленные варианты, но они не сработали. Наконец, после проверки исходного кода для "AbstractAuthorizationCodeServlet", я мог видеть, что перенаправление было бы отключено, если учетные данные присутствуют, но в идеале это должно было быть проверено на refresh token!=null, Я добавил код ниже CalendarSampleServlet и это сработало после этого. Большое облегчение после стольких часов разочарования. Слава Богу.

if (credential.getRefreshToken() == null) {
    AuthorizationCodeRequestUrl authorizationUrl = authFlow.newAuthorizationUrl();
    authorizationUrl.setRedirectUri(getRedirectUri(req));
    onAuthorization(req, resp, authorizationUrl);
    credential = null;
}

Использование автономного доступа и подсказки: согласие хорошо для меня:

   auth2 = gapi.auth2.init({
                    client_id: '{cliend_id}' 
   });

   auth2.grantOfflineAccess({prompt:'consent'}).then(signInCallback); 
    #!/usr/bin/env perl

    use strict;
    use warnings;
    use 5.010_000;
    use utf8;
    binmode STDOUT, ":encoding(utf8)";

    use Text::CSV_XS;
    use FindBin;
    use lib $FindBin::Bin . '/../lib';
    use Net::Google::Spreadsheets::V4;

    use Net::Google::DataAPI::Auth::OAuth2;

    use lib 'lib';
    use Term::Prompt;
    use Net::Google::DataAPI::Auth::OAuth2;
    use Net::Google::Spreadsheets;
    use Data::Printer ;


    my $oauth2 = Net::Google::DataAPI::Auth::OAuth2->new(
         client_id => $ENV{CLIENT_ID},
         client_secret => $ENV{CLIENT_SECRET},
         scope => ['https://www.googleapis.com/auth/spreadsheets'],
    );
    my $url = $oauth2->authorize_url();
    # system("open '$url'");
    print "go to the following url with your browser \n" ;
    print "$url\n" ;
    my $code = prompt('x', 'paste code: ', '', '');
    my $objToken = $oauth2->get_access_token($code);

    my $refresh_token = $objToken->refresh_token() ;

    print "my refresh token is : \n" ;
    # debug p($refresh_token ) ;
    p ( $objToken ) ;


    my $gs = Net::Google::Spreadsheets::V4->new(
            client_id      => $ENV{CLIENT_ID}
         , client_secret  => $ENV{CLIENT_SECRET}
         , refresh_token  => $refresh_token
         , spreadsheet_id => '1hGNULaWpYwtnMDDPPkZT73zLGDUgv5blwJtK7hAiVIU'
    );

    my($content, $res);

    my $title = 'My foobar sheet';

    my $sheet = $gs->get_sheet(title => $title);

    # create a sheet if does not exit
    unless ($sheet) {
         ($content, $res) = $gs->request(
              POST => ':batchUpdate',
              {
                    requests => [
                         {
                              addSheet => {
                                    properties => {
                                         title => $title,
                                         index => 0,
                                    },
                              },
                         },
                    ],
              },
         );

         $sheet = $content->{replies}[0]{addSheet};
    }

    my $sheet_prop = $sheet->{properties};

    # clear all cells
    $gs->clear_sheet(sheet_id => $sheet_prop->{sheetId});

    # import data
    my @requests = ();
    my $idx = 0;

    my @rows = (
         [qw(name age favorite)], # header
         [qw(tarou 31 curry)],
         [qw(jirou 18 gyoza)],
         [qw(saburou 27 ramen)],
    );

    for my $row (@rows) {
         push @requests, {
              pasteData => {
                    coordinate => {
                         sheetId     => $sheet_prop->{sheetId},
                         rowIndex    => $idx++,
                         columnIndex => 0,
                    },
                    data => $gs->to_csv(@$row),
                    type => 'PASTE_NORMAL',
                    delimiter => ',',
              },
         };
    }

    # format a header row
    push @requests, {
         repeatCell => {
              range => {
                    sheetId       => $sheet_prop->{sheetId},
                    startRowIndex => 0,
                    endRowIndex   => 1,
              },
              cell => {
                    userEnteredFormat => {
                         backgroundColor => {
                              red   => 0.0,
                              green => 0.0,
                              blue  => 0.0,
                         },
                         horizontalAlignment => 'CENTER',
                         textFormat => {
                              foregroundColor => {
                                    red   => 1.0,
                                    green => 1.0,
                                    blue  => 1.0
                              },
                              bold => \1,
                         },
                    },
              },
              fields => 'userEnteredFormat(backgroundColor,textFormat,horizontalAlignment)',
         },
    };

    ($content, $res) = $gs->request(
         POST => ':batchUpdate',
         {
              requests => \@requests,
         },
    );

    exit;

    #Google Sheets API, v4

    # Scopes
    # https://www.googleapis.com/auth/drive   View and manage the files in your Google D# # i# rive
    # https://www.googleapis.com/auth/drive.file View and manage Google Drive files and folders that you have opened or created with this app
    # https://www.googleapis.com/auth/drive.readonly   View the files in your Google Drive
    # https://www.googleapis.com/auth/spreadsheets  View and manage your spreadsheets in Google Drive
    # https://www.googleapis.com/auth/spreadsheets.readonly  View your Google Spreadsheets

Мое решение было немного странным. Я перепробовал каждое решение, которое нашел в интернете, и ничего. Удивительно, но это сработало: удалите credentials.json, обновите, снова установите приложение в своей учетной записи. Новый файл credentials.json будет иметь токен обновления. Сделайте резервную копию этого файла где-нибудь. Затем продолжайте использовать ваше приложение, пока ошибка обновления токена не появится снова. Удалите файл crendetials.json, который теперь только с сообщением об ошибке (в моем случае это произошло), затем вставьте старый файл учетных данных в папку, готово! Прошла 1 неделя с тех пор, как я это сделал, и проблем больше не было.

Теперь Google отклонил эти параметры в моем запросе (access_type, prompt)...:(, и вообще нет кнопки "Отменить доступ". Я расстраиваюсь из-за того, что вернул свой refresh_token lol

ОБНОВЛЕНИЕ: я нашел ответ здесь:D вы можете получить токен обновления по запросу https://developers.google.com/identity/protocols/OAuth2WebServer

curl -H "Тип содержимого: application / x-www-form-urlencoded" \ https://accounts.google.com/o/oauth2/revoke?token={token}

Токен может быть токеном доступа или токеном обновления. Если токен является токеном доступа и имеет соответствующий токен обновления, токен обновления также будет аннулирован.

Если отзыв успешно обработан, то код состояния ответа равен 200. Для условий ошибки возвращается код состояния 400 вместе с кодом ошибки.

В моем случае я выполнил все вышеперечисленные шаги и все еще имел ту же проблему.

Я использую Python и пытаюсь получить учетные данные следующим образом (ссылка на документ, почему это должно работать ):

      creds = {
          'token': credentials.token,
          'refresh_token': credentials.refresh_token,
          'token_uri': credentials.token_uri,
          'client_id': credentials.client_id,
          'client_secret': credentials.client_secret,
          'scopes': credentials.scopes
}

который по какой-то причине возвращал все правильно, кроме токена обновления

изменение его на приведенное ниже сработало в конце концов.

      import json
creds = json.loads(credentials.to_json())

Добавление access_type=offlineк авторизации URL-адрес авторизации Google помог мне. Я использую Java и Spring framework.

Это код, который создает регистрацию клиента:

return CommonOAuth2Provider.GOOGLE
                    .getBuilder(client)
                    .scope("openid", "profile", "email", "https://www.googleapis.com/auth/gmail.send")
                    .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
                    .authorizationUri("https://accounts.google.com/o/oauth2/v2/auth?access_type=offline")
                    .clientId(clientId)
                    .redirectUriTemplate("{baseUrl}/{action}/oauth2/code/{registrationId}")
                    .clientSecret(clientSecret)
                    .build();

Важной частью здесь является URI авторизации, к которому ?access_type=offline прилагается.

Другие вопросы по тегам