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

270

Я хочу получить токен доступа от 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
}
Мухаммед Усман
источник
У меня была похожая проблема. Проверьте мой ответ здесь
Arithran

Ответы:

676

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

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

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

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

Рич Саттон
источник
20
Это не сработало для меня, но добавление параметра "access_type = offline", похоже, помогло: developers.google.com/accounts/docs/OAuth2WebServer#offline
Джесси,
87
Вам нужно access_type=offlineво всех случаях, когда вы хотите refresh_token.
ДанХ
5
Но как мне обновить токен после его истечения в этом случае?
vivek_jonam
5
@vivek_jonam Сохраните маркер обновления и дату окончания срока действия. Когда он истекает, вы запрашиваете новый токен, используя токен обновления. Смотрите здесь: developers.google.com/accounts/docs/OAuth2WebServer#refresh
gelviis
4
Я получил это работает с $client->setAccessType('offline'). По умолчанию function setApprovalPrompt()уже передано force.
Мой
57

Чтобы получить токен обновления, вы должны добавить оба, 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");
Гал Морад
источник
В узле: var authUrl = oauth2Client.generateAuthUrl ({access_type: 'offline', область действия: SCOPES, Appro_prompt: 'force'});
Джорис Манс
2
Возмутительно, что Google не учел это в своей документации или, по крайней мере, в документации php или oath2, на которую я смотрел в течение 7 часов. Почему в мире это не выделено жирным шрифтом в их документах
Колин
Спасибо! Документы здесь ( github.com/googlesamples/apps-script-oauth2 ) вводят в заблуждение относительно этого параметра. Когда я добавил Approval_prompt = Force, я наконец-то получил токен обновления.
Алексей Жевжик
28

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

Модифицированный 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);
Norbert
источник
Perfect @Norbert, это было именно то, что мне было нужно.
Эммануэль
Спасибо! Точный ответ на мой вопрос @Norbert
varun teja-MVT
16

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

При запросе доступа с использованием access_type=offlineи approval_prompt=forceпараметров , которые вы должны получить одновременно доступ маркеров и обновление маркеров. Доступа лексема истекает вскоре после вы получите его , и вам нужно будет обновить его.

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

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

jeteon
источник
1
У меня есть CMS, где разные пользователи используют разные учетные записи Google для подключения к API аналитики. Однако иногда несколько пользователей могут подключаться, используя одну и ту же корпоративную учетную запись Google, но каждый хочет получить доступ к разным учетным записям Google Analytics. Только первый получает токен обновления, в то время как все остальные этого не делают и, следовательно, должны повторно подключаться каждый час. Разве нет способа получить ЖЕ ЖЕ токен обновления для последующих аутентификаций, а не только access_token, срок действия которого истекает в течение часа?
SsjCosty
1
Похоже, API создает токен обновления ровно один раз. Любое «разделение» токена должно произойти в вашем коде. Вы должны быть осторожны, чтобы случайно не дать пользователям новые привилегии доступа. Простой способ сделать это - заставить приложение отслеживать токены обновления и связанные учетные записи в своем собственном хранилище (отдельная «таблица» в SQLese). Затем, когда вы хотите получить новый токен доступа, вы проверяете и используете этот, возможно, общий токен оттуда. Реализованный определенным образом, ваш код не должен знать, кто на самом деле получил токен.
Jeteon
1
Я не знаю, как определить, какой токен обновления следует связать с новым токеном доступа, который я только что получил. Существуют разные пользователи, которые выполняют вход, и единственное, что у них общего, - это то, что они используют одну и ту же учетную запись Google (электронную почту) для подключения к API. Но Google не отправляет обратно идентификатор учетной записи или электронной почты, он просто отправляет токен. Так что я не знаю, как связать 2 разных пользователей CMS ...
SsjCosty
Я полностью объяснил свою проблему здесь: stackoverflow.com/questions/30217524/…
SsjCosty
Youtube oAuth2 refresh_token показывается только при использовании силы.
Дмитрий Полушкин
7

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

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

Зак Моррис
источник
Очень мелкие детали, но важная. Спас меня! Спасибо :)
Декстер
@ZackMorris Итак ... ты хочешь сказать, что я не могу получить refresh_token из бэкэнда, используя токен доступа?
Никогда больше
@ Никогда Вы не можете получить освежительный токен от самого access_token. Если вы хотите, чтобы ваш сервер обрабатывал обновления, вам нужно будет сначала сохранить refresh_token в вашей базе данных. Также, если вы выполняете поток OAuth-клиента на внешнем интерфейсе, пользователи должны будут отправлять свои refresh_token на сервер, если они хотят, чтобы сервер обновлял их.
Зак Моррис
4

Для того, чтобы получить, 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. Существует несколько библиотек, предоставленных Google, которые будут проверять и «декодировать» это для вас (я использовал гем Ruby google-id-token ). После того, как вы его проанализируете, этот subпараметр фактически станет уникальным идентификатором учетной записи Google.

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

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

coreyward
источник
Я думаю, что ключевым моментом является идентификация пользователей без сохранения личной информации. Спасибо за указание на это, я не видел ссылок на Google Docs об этом.
Danielo515,
3

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

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

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

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

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

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

Если вам снова понадобится «refresh_token», то вам нужно удалить доступ к своему приложению, выполнив шаги, описанные в ответе Рича Саттона .

Мазахир Харун
источник
2

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

$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');
Apadana
источник
1

Для меня я пробовал 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;
}
Anoop Исаак
источник
0

Теперь 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 вместе с кодом ошибки.

Tran Tien Duc
источник
0
    #!/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
Йордан Георгиев
источник
0

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

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

   auth2.grantOfflineAccess({prompt:'consent'}).then(signInCallback); 
Карлос Эдуардо Ки Ли
источник
0

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

Гильерме Каноа
источник
0

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

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

Мартин Каск
источник