Не отримує маркер оновлення Google OAuth


270

Я хочу отримати маркер доступу від Google. API Google говорить, що щоб отримати маркер доступу, надішліть код та інші параметри на сторінку, що генерує маркер, і відповідь буде об’єктом 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
}

У мене було подібне питання. Перевірте мою відповідь тут
Арітран

Відповіді:


675

Це refresh_tokenнадається лише при першій авторизації від користувача. Подальші авторизації, такі як вид, який ви робите під час тестування інтеграції OAuth2, знову не повернуться refresh_token. :)

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

Ви також можете додати параметри запиту prompt=consent&access_type=offlineдо переадресації на OAuth (див. Сторінку OAuth 2.0 для додатків веб-сервера Google ).

Це запропонує користувачеві знову авторизувати додаток і завжди повертатиме a refresh_token.


20
Це не спрацювало для мене, але додавання парама "access_type = offline", здавалося, зробило трюк: developers.google.com/accounts/docs/OAuth2WebServer#offline
Джессі

87
Це потрібно access_type=offlineу всіх випадках, коли ви хочете refresh_token.
DanH

5
Але як я оновлюю маркер після закінчення терміну його дії в цьому випадку?
vivek_jonam

5
@vivek_jonam Збережіть маркер оновлення та термін придатності. Після закінчення терміну дії ви запитуєте новий маркер, використовуючи маркер оновлення. Дивіться тут: developers.google.com/accounts/docs/OAuth2WebServer#refresh
gelviis

4
З цим я працював $client->setAccessType('offline'). function setApprovalPrompt()Вже пройшли в force, за замовчуванням.
moey

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: 'оффлайн', область дії: ОБЛАСТИ, одобрение_промп: 'сила'});
Joris Mans

2
Прикро, що Google не звертався до цього в своїй документації або, принаймні, не в документації на php чи oath2, на яку я дивився 7 годин. Чому в світі це не є великим жирним текстом у їхніх документах
Колін Рікельс

Дякую! Документи тут ( github.com/googlesamples/apps-script-oauth2 ) дуже вводять в оману щодо цього параметра. Коли я додав одобрение_промп = сила, я нарешті отримав маркер оновлення.
Олексій Жевжик

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

Ви повинні зберігати його зараз;)

Коли ваш таймер доступу просто закінчується

$client->refreshToken($theRefreshTokenYouHadStored);

Ідеально @Norbert, саме це мені було потрібно.
Еммануїл

Дякую! Точна відповідь на моє запитання @Norbert
varun teja-MVT

16

Це викликало у мене певну плутанину, тому я подумав, що поділюсь тим, що прийшов, щоб навчитися важко:

Коли ви запитуєте доступ за допомогою параметрів access_type=offlineта approval_prompt=forceпараметрів, вам слід отримати як маркер доступу, так і маркер оновлення . Доступу лексема закінчується незабаром після ви отримаєте його , і вам потрібно буде оновити його.

Ви правильно зробили запит на отримання нового маркера доступу та отримали відповідь, що містить ваш новий маркер доступу . Мене також бентежило те, що я не отримав новий маркер оновлення . Однак так мається на увазі, оскільки ви можете використовувати один і той же маркер оновлення знову і знову.

Я думаю, що деякі інші відповіді припускають, що ви хотіли отримати собі новий маркер оновлення з якоїсь причини і запропонували вам повторно авторизувати користувача, але насправді вам цього не потрібно, оскільки маркер оновлення, який ви працюєте, буде працювати до відкликаний користувачем.


1
У мене є CMS, де різні користувачі використовують різні облікові записи google для підключення до аналітики api. Однак іноді кілька користувачів можуть підключитися за допомогою одного і того ж корпоративного облікового запису google, але кожен, хто бажає отримати доступ до інших облікових записів Analytics. Тільки перший отримує маркер оновлення, тоді як всі інші не мають і тому повинні підключатися щогодини. Чи не існує способу отримати маркер оновлення SAME для подальшої автентифікації, а не просто access_token, який закінчується протягом години?
SsjCosty

1
Здається, API видає маркер оновлення точно один раз. Будь-яке "спільне використання" маркера повинно відбуватися у вашому коді. Вам слід бути обережним, щоб випадково не надавати користувачам нові пільги доступу. Простий спосіб зробити це - додаток відслідковувати маркери оновлення та пов’язані з ними акаунти у власному сховищі (окрема 'таблиця' у SQLese). Потім, коли ви хочете отримати новий маркер доступу, ви перевіряєте та використовуєте цей, можливо, загальний маркер звідти. Реалізований певним чином, ваш код не повинен знати, хто насправді отримав маркер.
jeteon

1
Я не знаю, як я міг би визначити, який маркер оновлення я повинен асоціювати з новим маркером доступу, який я щойно отримав. Є різні користувачі, які виконують вхід, і єдине, що у них є спільним, - це те, що вони використовують один і той же обліковий запис Google (електронну пошту) для підключення до API. Але Google не надсилає назад ідентифікатор облікового запису чи електронну пошту, він просто надсилає назад маркер. Тож я не знаю, як пов’язати двох різних користувачів CMS ...
SsjCosty

Я повністю пояснив моє запитання тут: stackoverflow.com/questions/30217524 / ...
SsjCosty

Youtube oAuth2 refresh_token показано лише при застосуванні сили.
Дмитро Полушкін

7

Відповідь Річ Саттона, нарешті, спрацювала для мене, після того як я зрозумів, що додавання access_type=offlineробиться на запит клієнта на передньому кінці про авторизаційний код, а не на запит зворотнього кінця, який обмінює цей код на access_token. Я додав коментар до його відповіді та це посилання в Google для отримання додаткової інформації про оновлення жетонів.

PS Якщо ви використовуєте Satellizer, ось як додати цю опцію до $ authProvider.google в AngularJS .


Дуже незначні деталі, але важливі. Врятували мене! Дякую :)
Dexter

@ZackMorris Отож .. чи ти хочеш сказати, що я не можу отримати refresh_token з бекенда за допомогою маркера доступу?
Ніколи більше

@ Ніколи більше не можна отримати refresh_token від самого доступу_token. Якщо ви хочете, щоб ваш сервер обробляв оновлення, вам потрібно буде вперше зберегти refresh_token у вашій базі даних. Крім того, якщо ви робите клієнтський потік OAuth на передньому кінці, користувачі повинні будуть надіслати їх refresh_token на задній кінець, якщо вони хочуть, щоб сервер оновив для них.
Зак Морріс

4

Для того, щоб отримати те, refresh_tokenвам потрібно включити access_type=offlineв URL-адресу запиту OAuth. Коли користувач вперше засвідчить автентифікацію, ви отримаєте назад ненульовий показник refresh_token, а також access_tokenтермін дії, який закінчується.

Якщо у вас виникає ситуація, коли користувач може повторно засвідчити обліковий запис, для якого ви вже маєте маркер аутентифікації (наприклад, @SsjCosty згадується вище), вам потрібно повернути інформацію від Google, для якого облікового запису використовується маркер. Для цього додайте profileдо своїх областей. Використовуючи дорогоцінний камінь OAuth2 Ruby, ваш остаточний запит може виглядати приблизно так:

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 (тобто ви не використовуєте це для входу / автентифікації) .


Я вважаю, що ключова частина щодо визначення користувачів без збереження будь-якої особистої інформації є ключовою. Дякуємо, що вказали на це, я не бачив жодних посилань на документи Google про це.
Danielo515

3

1. Як отримати "refresh_token"?

Рішення: параметр access_type = 'офлайн' слід використовувати при створенні 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');

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

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 разом із кодом помилки.


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

Моє рішення було трохи дивним ... я спробував кожне рішення, яке я знайшов в Інтернеті, і нічого. Дивно це спрацювало: видаліть обліковий запис.json, оновіть, ще раз вінулюйте додаток у своєму акаунті. Новий файл ententials.json матиме маркер оновлення. Завантажте цей файл десь. Потім продовжуйте використовувати додаток, поки помилка маркера оновлення не з’явиться знову. Видаліть файл crendetials.json, який наразі є лише повідомленням про помилку (це сталося в моєму випадку), а потім вставте старий файл облікових даних у папку, зроблено! З того часу, як я зробив це, минуло 1 тиждень, і більше проблем не було.


0

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

При використанні облікових даних типу "Веб-додаток" жодна комбінація змінних підказок / затвердження_промп не працюватиме - ви все одно отримаєте refresh_token лише за першого запиту.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.