Nie otrzymuję tokenu odświeżania Google OAuth

270

Chcę uzyskać token dostępu od Google. Interfejs API Google mówi, że aby uzyskać token dostępu, wyślij kod i inne parametry na stronę generującą token, a odpowiedzią będzie Obiekt JSON, taki jak:

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

Nie otrzymuję jednak tokenu odświeżania. W moim przypadku odpowiedź brzmi:

{
 "access_token" : "ya29.sddsdsdsdsds_h9v_nF0IR7XcwDK8XFB2EbvtxmgvB-4oZ8oU",
"token_type" : "Bearer",
"expires_in" : 3600
}
Muhammad Usman
źródło
Miałem podobny problem. Sprawdź moją odpowiedź tutaj
Arithran,

Odpowiedzi:

675

refresh_tokenJest tylko na pierwszej autoryzacji ze strony użytkownika. Późniejsze autoryzacje, takie jak dokonane podczas testowania integracji OAuth2, nie zwrócą go refresh_tokenponownie. :)

  1. Przejdź do strony pokazującej aplikacje z dostępem do konta: https://myaccount.google.com/u/0/permissions .
  2. W menu Aplikacje innych firm wybierz swoją aplikację.
  3. Kliknij opcję Usuń dostęp, a następnie kliknij przycisk OK, aby potwierdzić
  4. Następne żądanie OAuth2 zwróci refresh_token(pod warunkiem, że zawiera również parametr zapytania „access_type = offline”).

Alternatywnie możesz dodać parametry zapytania prompt=consent&access_type=offlinedo przekierowania OAuth (patrz strona Google OAuth 2.0 dla aplikacji serwera WWW ).

Spowoduje to, że użytkownik ponownie autoryzuje aplikację i zawsze zwróci a refresh_token.

Rich Sutton
źródło
20
Nie działało to dla mnie, ale dodanie parametru „access_type = offline” wydawało się załatwić sprawę: developers.google.com/accounts/docs/OAuth2WebServer#offline
Jesse
87
Potrzebujesz access_type=offlinewe wszystkich przypadkach, kiedy chcesz refresh_token.
DanH
5
Ale jak w takim przypadku mogę odświeżyć token po jego wygaśnięciu?
vivek_jonam
5
@vivek_jonam Przechowuj token odświeżania i datę ważności. Po wygaśnięciu zamawiasz nowy token za pomocą tokena odświeżania. Zobacz tutaj: developers.google.com/accounts/docs/OAuth2WebServer#refresh
gelviis
4
Mam to do pracy $client->setAccessType('offline'). function setApprovalPrompt()Został już przekazany forcedomyślnie.
moey,
57

W celu uzyskania odświeżenie tokena trzeba dodać zarówno approval_prompt=forcei access_type="offline" Jeśli używasz klienta java udostępnianej przez Google będzie wyglądać następująco:

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

AuthorizationCodeRequestUrl authorizationUrl =
            flow.newAuthorizationUrl().setRedirectUri(callBackUrl)
                    .setApprovalPrompt("force")
                    .setAccessType("offline");
Gal Morad
źródło
W węźle: var authUrl = oauth2Client.generateAuthUrl ({typ_dostępu: „offline”, zakres: ZAKRESY, monit o zatwierdzenie: „życie”});
Joris Mans
2
To oburzające, że Google nie rozwiązał tego w swojej dokumentacji, a przynajmniej nie w dokumentacji php lub oath2, na którą patrzyłem od 7 godzin. Dlaczego na świecie nie jest to
napisane
Dziękuję Ci! Dokumenty tutaj ( github.com/googlesamples/apps-script-oauth2 ) bardzo wprowadzają w błąd odnośnie tego parametru. Kiedy dodałem zatwierdzenie_prompt = życie, w końcu dostałem token odświeżania.
Alex Zhevzhik
28

Przeszukałem długą noc, a to załatwia sprawę:

Zmodyfikowano user-example.php z admin-sdk

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

następnie otrzymasz kod na adres przekierowania i uwierzytelnienie za pomocą kodu i pobranie tokena odświeżania

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

Powinieneś to teraz zapisać;)

Kiedy twój klucz dostępu przekroczy limit czasu, po prostu zrób to

$client->refreshToken($theRefreshTokenYouHadStored);
Norbert
źródło
Idealny @ Norbert, Właśnie tego potrzebowałem.
Emmanuel,
Dzięki! Dokładna odpowiedź na moje pytanie @Norbert
varun teja-MVT
16

To spowodowało pewne zamieszanie, więc pomyślałem, że podzielę się tym, co przyszedłem nauczyć się na własnej skórze:

Gdy poprosisz o dostęp za pomocą parametrów access_type=offlinei approval_prompt=force, powinieneś otrzymać zarówno token dostępu, jak i token odświeżania . Dostęp tokenu wygasa zaraz po otrzymaniu go i będzie trzeba go odświeżyć.

Prawidłowo złożyłeś prośbę o uzyskanie nowego tokena dostępu i otrzymałeś odpowiedź z nowym tokenem dostępu . Byłem również zdezorientowany faktem, że nie dostałem nowego tokena odświeżania . Jednak tak właśnie powinno być, ponieważ możesz używać tego samego tokena odświeżania w kółko.

Wydaje mi się, że niektóre inne odpowiedzi zakładają, że z jakiegoś powodu chciałeś zdobyć nowy token odświeżania i zasugerowałem, że ponownie autoryzujesz użytkownika, ale w rzeczywistości nie jest to konieczne, ponieważ token odświeżania będzie działał do odwołany przez użytkownika.

jeteon
źródło
1
Mam CMS, w którym różni użytkownicy używają różnych kont Google do łączenia się z interfejsem API do analizy. Czasami jednak kilku użytkowników może połączyć się przy użyciu tego samego firmowego konta Google, ale każdy chce uzyskać dostęp do innego konta Analytics. Tylko pierwszy otrzymuje token odświeżania, podczas gdy wszyscy inni nie i dlatego muszą łączyć się co godzinę. Czy nie ma sposobu, aby uzyskać SAMY token odświeżania do późniejszych uwierzytelnień zamiast samego tokenu access_token, który wygasa w ciągu godziny?
SsjCosty
1
Wygląda na to, że interfejs API generuje token odświeżania dokładnie raz. Wszelkie „udostępnianie” tokena musiałoby mieć miejsce w kodzie. Trzeba jednak uważać, aby przypadkowo nie dać użytkownikom nowych uprawnień dostępu. Prostym sposobem na to jest śledzenie tokenów odświeżania i powiązanych kont we własnej pamięci (osobna „tabela” w SQLese). Następnie, gdy chcesz uzyskać nowy token dostępu , sprawdź, czy stamtąd możesz użyć tego potencjalnie wspólnego tokena. Zaimplementowany w określony sposób, twój kod nie musi wiedzieć, kto faktycznie dostał token.
jeteon
1
Nie wiem, jak mogłem zidentyfikować token odświeżania, który powinienem powiązać z nowym tokenem dostępu, który właśnie dostałem. Są różni użytkownicy, którzy logują się, a jedyną wspólną cechą jest to, że używają tego samego konta Google (e-mail) do łączenia się z interfejsem API. Ale Google nie odsyła identyfikatora konta ani e-maila, po prostu odsyła token. Więc nie wiem, jak powiązać 2 różnych użytkowników CMS ...
SsjCosty
W pełni wyjaśniłem mój problem tutaj: stackoverflow.com/questions/30217524/…
SsjCosty
Youtube oAuth2 token_odświeżania jest wyświetlany tylko po użyciu siły.
Dmitry Polushkin
7

Odpowiedź Richa Suttona w końcu zadziałała dla mnie, gdy zdałem sobie sprawę, że dodawanie access_type=offlineodbywa się na żądanie klienta frontonu dotyczące kodu autoryzacji, a nie na żądanie zaplecza, które wymienia ten kod na access_token. Dodałem komentarz do jego odpowiedzi i ten link w Google, aby uzyskać więcej informacji na temat odświeżania tokenów.

PS Jeśli używasz Satellizera, oto jak dodać tę opcję do $ authProvider.google w AngularJS .

Zack Morris
źródło
Bardzo drobne szczegóły, ale ważne. Uratował mnie! Dzięki :)
Dexter
@ZackMorris Więc .. masz na myśli powiedzieć, że nie mogę uzyskać refresh_token z backendu za pomocą tokena dostępu?
Nevermore
@Nevermore Nie można uzyskać tokena_odświeżania z samego tokena_dostępu. Jeśli chcesz, aby Twój serwer obsługiwał odświeżenia, musisz po raz pierwszy zapisać token_odświeżania w bazie danych. Również jeśli wykonujesz przepływ OAuth klienta na interfejsie, użytkownicy będą musieli wysłać swój moduł_odświeżania do zaplecza, jeśli chcą, aby serwer dla nich odświeżył.
Zack Morris,
4

Aby je uzyskać refresh_token, musisz podać access_type=offlineadres URL żądania OAuth. Gdy użytkownik uwierzytelni się po raz pierwszy, otrzymasz zarówno wartość zerową, refresh_tokenjak i access_tokenwygasającą.

Jeśli masz sytuację, w której użytkownik może ponownie uwierzytelnić konto, dla którego masz już token uwierzytelnienia (jak wspomniano powyżej w @SsjCosty), musisz odzyskać informacje od Google, dla którego konta jest token. Aby to zrobić, dodaj profiledo swoich zakresów. Korzystając z klejnotu Ruby OAuth2, Twoja ostatnia prośba może wyglądać mniej więcej tak:

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"
)

Zauważ, że zakres ma dwa oddzielone spacjami wpisy, jeden dla dostępu tylko do odczytu do Google Analytics, a drugi jest tylko profile, który jest standardem OpenID Connect.

Spowoduje to, że Google dostarczy dodatkowy atrybut wywoływany id_tokenw get_tokenodpowiedzi. Aby uzyskać informacje z id_token, sprawdź tę stronę w dokumentach Google. Istnieje kilka bibliotek dostarczonych przez Google, które sprawdzają i „dekodują” to za Ciebie (użyłem klejnotu Ruby google-id-token ). Po przeanalizowaniu subparametr ten jest faktycznie unikalnym identyfikatorem konta Google.

Warto zauważyć, że jeśli zmienisz zakres, ponownie otrzymasz token odświeżania dla użytkowników, którzy już uwierzytelnili się w oryginalnym zakresie. Jest to przydatne, jeśli, powiedzmy, masz już grupę użytkowników i nie chcesz, aby wszyscy cofnęli autoryzację aplikacji w Google.

Aha, i ostatnia uwaga: nie potrzebujesz prompt=select_account , ale jest to przydatne, jeśli masz sytuację, w której użytkownicy mogą chcieć uwierzytelnić się za pomocą więcej niż jednego konta Google (tj. Nie używasz tego do logowania / uwierzytelniania) .

coreyward
źródło
Myślę, że kluczowa jest część dotycząca identyfikowania użytkowników bez przechowywania jakichkolwiek danych osobowych. Dzięki za zwrócenie na to uwagi, nie widziałem żadnych odniesień w dokumentach Google na ten temat.
Danielo515,
3

1. Jak zdobyć „refresh_token”?

Rozwiązanie: przy generowaniu authURL należy użyć opcji access_type = 'offline'. źródło: Korzystanie z OAuth 2.0 dla aplikacji serwera WWW

2. Ale nawet z „access_type = offline”, nie otrzymuję „refresh_token”?

Rozwiązanie: pamiętaj, że otrzymasz go tylko na pierwsze żądanie, więc jeśli gdzieś go przechowujesz i istnieje przepis, aby nadpisać ten kod w swoim kodzie podczas uzyskiwania nowego tokena dostępu po upływie poprzedniego okresu, pamiętaj, aby nie zastąpić tej wartości.

Z Google Auth Doc: (ta wartość = typ_dostępu)

Ta wartość instruuje serwer autoryzacji Google, aby zwrócił token odświeżania i token dostępu przy pierwszej wymianie kodu autoryzacji przez aplikację na tokeny.

Jeśli ponownie potrzebujesz „refresh_token”, musisz usunąć dostęp do swojej aplikacji, wykonując czynności opisane w odpowiedzi Richa Suttona .

Mazahir Haroon
źródło
2

Ustawienie tego spowoduje, że token odświeżania będzie wysyłany za każdym razem:

$client->setApprovalPrompt('force');

przykład podano poniżej (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
źródło
1

Dla mnie próbowałem CalendarSampleServletdostarczone przez Google. Po 1 godzinie upływa limit czasu dostępu i następuje przekierowanie do strony 401. Próbowałem wszystkich powyższych opcji, ale one nie działały. Wreszcie po sprawdzeniu kodu źródłowego „AbstractAuthorizationCodeServlet” zauważyłem, że przekierowanie zostanie wyłączone, jeśli poświadczenia są obecne, ale najlepiej byłoby sprawdzić refresh token!=null. Dodałem poniższy kod CalendarSampleServleti potem zadziałało. Wielka ulga po tylu godzinach frustracji. Dzięki Bogu.

if (credential.getRefreshToken() == null) {
    AuthorizationCodeRequestUrl authorizationUrl = authFlow.newAuthorizationUrl();
    authorizationUrl.setRedirectUri(getRedirectUri(req));
    onAuthorization(req, resp, authorizationUrl);
    credential = null;
}
Anoop Isaac
źródło
0

teraz Google odrzucił te parametry w moim żądaniu (typ_podpowiedzi, monit) ... :( i nie ma w ogóle przycisku „Odwołaj dostęp”. Jestem frustrujący z powodu odzyskania mojego lol_odświeżania

AKTUALIZACJA: Znalazłem odpowiedź tutaj: D możesz odzyskać token odświeżania przez żądanie https://developers.google.com/identity/protocols/OAuth2WebServer

curl -H „Typ zawartości: application / x-www-form-urlencoded” \ https://accounts.google.com/o/oauth2/revoke?token= {token}

Token może być tokenem dostępu lub tokenem odświeżania. Jeśli token jest tokenem dostępu i ma odpowiedni token odświeżania, token odświeżania również zostanie odwołany.

Jeśli odwołanie zostanie pomyślnie przetworzone, wówczas kod stanu odpowiedzi wynosi 200. W przypadku warunków błędu zwracany jest kod stanu 400 wraz z kodem błędu.

Tran Tien Duc
źródło
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
Yordan Georgiev
źródło
0

Korzystanie z dostępu offline i monit: zgoda zadziałała dobrze:

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

   auth2.grantOfflineAccess({prompt:'consent'}).then(signInCallback); 
Carlos Eduardo Ki Lee
źródło
0

Moje rozwiązanie było trochę dziwne. Próbowałem każdego rozwiązania znalezionego w Internecie i nic. Niespodziewanie to zadziałało: usuń poświadczenia. Json, odśwież, ponownie oblicz aplikację na koncie. Nowy plik credentials.json będzie miał token odświeżania. Utwórz kopię zapasową gdzieś tego pliku. Następnie używaj aplikacji, dopóki błąd tokena odświeżania nie pojawi się ponownie. Usuń plik crendetials.json, który jest teraz tylko z komunikatem o błędzie (w moim przypadku tak się stało), a następnie wklej stary plik poświadczeń do folderu, gotowe! Minął już tydzień, odkąd to zrobiłem i nie miałem już żadnych problemów.

Guilherme Canoa
źródło
0

Aby za każdym razem uzyskiwać nowy token_odświeżania podczas uwierzytelniania, typ poświadczeń OAuth 2.0 utworzonych na pulpicie nawigacyjnym powinien mieć wartość „Inne”. Jak wspomniano powyżej, podczas generowania authURL należy użyć opcji access_type = 'offline'.

Podczas korzystania z poświadczeń typu „Aplikacja internetowa” żadna kombinacja zmiennych zachęty / zatwierdzenia_prompt nie będzie działać - nadal otrzymasz argument_odświeżania tylko na pierwsze żądanie.

Martin Kask
źródło