Non riceve il token di aggiornamento di Google OAuth


270

Voglio ottenere il token di accesso da Google. L'API di Google afferma che per ottenere il token di accesso, inviare il codice e altri parametri alla pagina di generazione del token e la risposta sarà un oggetto JSON come:

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

Tuttavia, non ricevo il token di aggiornamento. La risposta nel mio caso è:

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

Ho avuto un problema simile. Controlla qui la
Arithran,

Risposte:


675

L' refresh_tokenè fornito solo sulla prima autorizzazione da parte dell'utente. Le autorizzazioni successive, come il tipo che si effettua durante il test di un'integrazione OAuth2, non restituiranno refresh_tokennuovamente. :)

  1. Vai alla pagina che mostra le app con accesso al tuo account: https://myaccount.google.com/u/0/permissions .
  2. Nel menu App di terze parti, scegli la tua app.
  3. Fai clic su Rimuovi accesso, quindi su OK per confermare
  4. La successiva richiesta OAuth2 che fai restituirà un refresh_token(a condizione che includa anche il parametro di query 'access_type = offline'.

In alternativa, è possibile aggiungere i parametri della query prompt=consent&access_type=offlineal reindirizzamento di OAuth (consultare la pagina OAuth 2.0 di Google per le applicazioni del server Web ).

Ciò richiederà all'utente di autorizzare nuovamente l'applicazione e restituirà sempre a refresh_token.


20
Questo non ha funzionato per me, ma l'aggiunta del parametro "access_type = offline" sembrava fare il trucco: developers.google.com/accounts/docs/OAuth2WebServer#offline
Jesse

87
È necessario access_type=offlinein tutti i casi in cui si desidera refresh_token.
Dan

5
Ma come posso aggiornare il token dopo la sua scadenza in questo caso?
vivek_jonam,

5
@vivek_jonam Memorizza il token di aggiornamento e la data di scadenza. Alla scadenza, richiedi un nuovo token utilizzando il token di aggiornamento. Vedi qui: developers.google.com/accounts/docs/OAuth2WebServer#refresh
gelviis

4
Ho capito che funziona $client->setAccessType('offline'). Il function setApprovalPrompt()è già passato in force, per impostazione predefinita.
Moey,

57

Per ottenere il token di aggiornamento è necessario aggiungere entrambi approval_prompt=forcee access_type="offline" se si utilizza il client Java fornito da Google sarà simile al seguente:

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

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

Nel nodo: var authUrl = oauth2Client.generateAuthUrl ({access_type: 'offline', scope: SCOPES, approvazione_prompt: 'force'});
Joris Mans

2
È scandaloso che Google non abbia affrontato questo aspetto nella loro documentazione o almeno non nella documentazione php o oath2 che sto fissando da 7 ore. Perché nel mondo questo non è in grande testo in grassetto nei loro documenti
Colin Rickels,

Grazie! I documenti qui ( github.com/googlesamples/apps-script-oauth2 ) sono molto fuorvianti su questo parametro. Quando ho aggiunto approvazione_prompt = force ho finalmente ottenuto un token di aggiornamento.
Alex Zhevzhik,

28

Ho cercato una lunga notte e questo è il trucco:

User-example.php modificato da admin-sdk

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

quindi ottieni il codice nell'URL di reindirizzamento e l'autenticazione con il codice e il token di aggiornamento

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

Dovresti memorizzarlo ora;)

Quando il tuo accesskey scade e basta

$client->refreshToken($theRefreshTokenYouHadStored);

Perfetto @Norbert, questo era esattamente ciò di cui avevo bisogno.
Emmanuel,

Grazie! Risposta esatta per la mia domanda @Norbert
varun teja-MVT

16

Questo mi ha causato un po 'di confusione, quindi ho pensato di condividere ciò che sono venuto per imparare nel modo più duro:

Quando si richiede l'accesso utilizzando i parametri access_type=offlinee approval_prompt=force, è necessario ricevere sia un token di accesso sia un token di aggiornamento . Il token di accesso scade subito dopo averlo ricevuto e sarà necessario aggiornarlo.

Hai correttamente inviato la richiesta per ottenere un nuovo token di accesso e hai ricevuto la risposta con il tuo nuovo token di accesso . Sono stato anche confuso dal fatto che non ho ricevuto un nuovo token di aggiornamento . Tuttavia, è così che deve essere poiché è possibile utilizzare lo stesso token di aggiornamento più e più volte.

Penso che alcune delle altre risposte presumano che tu volessi procurarti un nuovo token di aggiornamento per qualche motivo e suggerito di autorizzare nuovamente l'utente, ma in realtà non è necessario poiché il token di aggiornamento che hai funzionerà fino al revocato dall'utente.


1
Ho un CMS in cui diversi utenti utilizzano account Google diversi per connettersi all'API di analisi. Tuttavia, a volte diversi utenti possono connettersi utilizzando lo stesso account google aziendale, ma ognuno di essi desidera accedere a un account Analytics diverso. Solo il primo riceve il token di aggiornamento, mentre tutti gli altri no e quindi devono riconnettersi ogni ora. Non esiste un modo per ottenere il token di aggiornamento SAME per le autenticazioni successive anziché solo il token di accesso che scade entro un'ora?
SsjCosty,

1
L'API sembra produrre il token di aggiornamento esattamente una volta. Qualsiasi "condivisione" del token dovrebbe avvenire nel tuo codice. Tuttavia, dovresti stare attento a non dare accidentalmente nuovi privilegi di accesso agli utenti. Un modo semplice per farlo è fare in modo che l'applicazione tenga traccia dei token di aggiornamento e degli account associati nella propria memoria ('tabella' separata in SQLese). Quindi, quando si desidera ottenere un nuovo token di accesso , verificare e utilizzare questo token forse comune da lì. Implementato in un certo modo, il tuo codice non ha bisogno di sapere chi ha effettivamente ottenuto il token.
jeteon,

1
Non so come identificare il token di aggiornamento che dovrei associare a un nuovo token di accesso che ho appena ricevuto. Esistono diversi utenti che effettuano l'accesso e l'unica cosa che hanno in comune è che usano lo stesso account Google (e-mail) per connettersi all'API. Ma Google non restituisce un ID dell'account o dell'e-mail, ma restituisce semplicemente un token. Quindi non so come associare i 2 diversi utenti CMS ...
SsjCosty,

Ho ampiamente spiegato la mia edizione qui: stackoverflow.com/questions/30217524/...
SsjCosty

Youtube oAuth2 refresh_token mostrato solo quando usato forza.
Dmitry Polushkin,

7

La risposta di Rich Sutton alla fine ha funzionato per me, dopo che ho capito che l'aggiunta access_type=offlineè fatta sulla richiesta del client front-end per un codice di autorizzazione, non sulla richiesta di back-end che scambia quel codice con un access_token. Ho aggiunto un commento alla sua risposta e questo link su Google per ulteriori informazioni sui token di aggiornamento.

PS Se stai usando Satellizer, ecco come aggiungere quell'opzione a $ authProvider.google in AngularJS .


Dettagli molto minori ma importanti. Mi ha salvato ! Grazie :)
Dexter

@ZackMorris Quindi ... vuoi dire che non riesco a ottenere refresh_token dal backend usando il token di accesso?
Mai più il

@Nevermore Non puoi ottenere un refresh_token dallo stesso access_token. Se si desidera che il proprio server gestisca gli aggiornamenti, è necessario archiviare il refresh_token nel database la prima volta. Inoltre, se stai eseguendo un flusso OAuth client sul front-end, gli utenti dovranno inviare il loro refresh_token al back-end se desiderano che il server si aggiorni per loro.
Zack Morris,

4

Per ottenere ciò refresh_tokenè necessario includere access_type=offlinenell'URL della richiesta OAuth. Quando un utente esegue l'autenticazione per la prima volta, verrà restituito un valore diverso da zero refresh_tokene uno access_tokenche scade.

Se si verifica una situazione in cui un utente potrebbe autenticare nuovamente un account per cui si dispone già di un token di autenticazione (come menzionato @SsjCosty sopra), è necessario recuperare informazioni da Google sull'account a cui è destinato il token. Per fare ciò, aggiungi profileai tuoi ambiti. Utilizzando la gemma OAuth2 Ruby, la tua richiesta finale potrebbe assomigliare a questa:

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

Si noti che l'ambito ha due voci delimitate da spazi, una per l'accesso in sola lettura a Google Analytics e l'altra è giusta profile, che è uno standard OpenID Connect.

Ciò comporterà che Google fornisca un attributo aggiuntivo chiamato id_token nella get_tokenrisposta. Per ottenere informazioni da id_token, controlla questa pagina nei documenti di Google. Ci sono una manciata di librerie fornite da Google che convalideranno e “decodificheranno” questo per te (ho usato la gemma Ruby google-id-token ). Una volta analizzato, il subparametro è effettivamente l'ID account Google univoco.

Vale la pena notare che se si modifica l'ambito, si riceverà nuovamente un token di aggiornamento per gli utenti che hanno già eseguito l'autenticazione con l'ambito originale. Ciò è utile se, ad esempio, hai già un sacco di utenti e non vuoi renderli tutti non autorizzati sull'app in Google.

Oh, e un'ultima nota: non è necessario prompt=select_account , ma è utile se hai una situazione in cui i tuoi utenti potrebbero voler autenticarsi con più di un account Google (ad esempio, non lo stai usando per l'accesso / autenticazione) .


Penso che la parte relativa all'identificazione degli utenti senza memorizzare alcuna informazione personale sia la chiave. Grazie per averlo sottolineato, non ho visto alcun riferimento sui documenti di Google al riguardo.
Danielo515,

3

1. Come ottenere 'refresh_token'?

Soluzione: è necessario utilizzare access_type = 'offline' quando si genera authURL. fonte: utilizzo di OAuth 2.0 per applicazioni Web Server

2. Ma anche con 'access_type = offline', non ottengo il 'refresh_token'?

Soluzione: tieni presente che lo otterrai solo alla prima richiesta, quindi se lo stai memorizzando da qualche parte e c'è una disposizione per sovrascriverlo nel tuo codice quando ricevi un nuovo access_token dopo scadenze precedenti, quindi assicurati di non sovrascrivere questo valore.

Da Google Auth Doc: (questo valore = access_type)

Questo valore indica al server di autorizzazione di Google di restituire un token di aggiornamento e un token di accesso la prima volta che l'applicazione scambia un codice di autorizzazione per i token.

Se hai bisogno di nuovo "refresh_token", devi rimuovere l'accesso per la tua app seguendo i passaggi scritti nella risposta di Rich Sutton .


2

Impostandolo, il token di aggiornamento verrà inviato ogni volta:

$client->setApprovalPrompt('force');

di seguito è riportato un esempio (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

Per me stavo provando CalendarSampleServletfornito da Google. Dopo 1 ora il tasto access_key scade e si passa a una pagina 401. Ho provato tutte le opzioni di cui sopra ma non hanno funzionato. Alla fine, controllando il codice sorgente per "AbstractAuthorizationCodeServlet" , ho potuto vedere che il reindirizzamento sarebbe disabilitato se fossero presenti credenziali, ma idealmente avrebbe dovuto verificare refresh token!=null. Ho aggiunto sotto il codice CalendarSampleServlete ha funzionato dopo. Grande sollievo dopo tante ore di frustrazione. Grazie Dio.

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

0

ora google aveva rifiutato quei parametri nella mia richiesta (access_type, prompt) ... :( e non esiste alcun pulsante "Revoke Access". Sono frustrante a causa del recupero del mio refresh_token lol

AGGIORNAMENTO: ho trovato la risposta qui: D è possibile recuperare il token di aggiornamento tramite una richiesta https://developers.google.com/identity/protocols/OAuth2WebServer

curl -H "Tipo di contenuto: application / x-www-form-urlencoded" \ https://accounts.google.com/o/oauth2/revoke?token= {token}

Il token può essere un token di accesso o un token di aggiornamento. Se il token è un token di accesso e ha un token di aggiornamento corrispondente, anche il token di aggiornamento verrà revocato.

Se la revoca viene elaborata correttamente, il codice di stato della risposta è 200. Per le condizioni di errore, viene restituito un codice di stato 400 insieme a un codice di errore.


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

Utilizzo dell'accesso offline e prompt: il consenso ha funzionato bene per me:

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

   auth2.grantOfflineAccess({prompt:'consent'}).then(signInCallback); 

0

La mia soluzione era un po 'strana..ho provato ogni soluzione che ho trovato su internet e niente. Sorprendentemente, ha funzionato: elimina le credentials.json, aggiorna, vincola nuovamente la tua app nel tuo account. Il nuovo file credentials.json avrà il token di aggiornamento. Esegui il backup di questo file da qualche parte. Quindi continua a utilizzare l'app fino a quando non si ripresenta l'errore del token di aggiornamento. Elimina il file crendetials.json che ora è solo con un messaggio di errore (questo è accaduto nel mio caso), quindi incolla il tuo vecchio file di credenziali nella cartella, il gioco è fatto! È passata 1 settimana da quando l'ho fatto e non ho più avuto problemi.


0

Per ottenere ogni nuovo refresh_token ogni volta all'autenticazione, il tipo di credenziali OAuth 2.0 create nella dashboard deve essere "Altro". Inoltre, come menzionato sopra, l'opzione access_type = 'offline' dovrebbe essere usata durante la generazione di authURL.

Quando si utilizzano credenziali con il tipo "Applicazione Web", nessuna combinazione di variabili prompt / Approvation_prompt funzionerà - si otterrà comunque il refresh_token solo alla prima richiesta.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.