No recibo el token de actualización de Google OAuth

Quiero obtener el token de acceso de Google. La API de Google dice que para obtener el token de acceso, envíe el código y otros parámetros a la página de generación de tokens, y la respuesta será un objeto JSON como:

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

Sin embargo, no recibo el token de actualización. La respuesta en mi caso es:

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

refresh_token solo se proporciona en la primera autorización del usuario. Las autorizaciones posteriores, como las que realiza al probar una integración de OAuth2, no devolverán nuevamente la refresh_token la refresh_token . 🙂

  1. Vaya a la página que muestra aplicaciones con acceso a su cuenta: https://myaccount.google.com/u/0/permissions .
  2. En el menú de aplicaciones de terceros, elige tu aplicación.
  3. Haga clic en Eliminar acceso y luego en Aceptar para confirmar
  4. La próxima solicitud de OAuth2 que realice devolverá un refresh_token .

Alternativamente, puede agregar el parámetro de consulta prompt=consent al redireccionamiento de OAuth (consulte la página OAuth 2.0 de Google para aplicaciones de servidor web).

Esto pedirá al usuario que autorice la aplicación nuevamente y siempre devolverá un refresh_token .

Para obtener el token de actualización, debes agregar tanto approval_prompt=force como access_type="offline" Si estás utilizando el cliente java provisto por Google, se verá así:

 GoogleAuthorizationCodeFlow flow = new GoogleAuthorizationCodeFlow.Builder( HTTP_TRANSPORT, JSON_FACTORY, getClientSecrets(), scopes) .build(); AuthorizationCodeRequestUrl authorizationUrl = flow.newAuthorizationUrl().setRedirectUri(callBackUrl) .setApprovalPrompt("force") .setAccessType("offline"); 

Busqué en una noche larga y esto está haciendo el truco:

Usuario-ejemplo.php modificado de admin-sdk

 $client->setAccessType('offline'); $client->setApprovalPrompt('force'); $authUrl = $client->createAuthUrl(); echo ""; 

luego obtienes el código en la url de redirección y la autenticación con el código y obteniendo el token de actualización

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

Deberías guardarlo ahora;)

Cuando su accesskey se apaga, solo haga

 $client->refreshToken($theRefreshTokenYouHadStored); 

Esto me ha causado cierta confusión, así que pensé en compartir lo que he aprendido por las malas:

Cuando solicite acceso utilizando los access_type=offline y approval_prompt=force , debe recibir un token de acceso y un token de actualización . El token de acceso caduca al poco tiempo de recibirlo y deberá actualizarlo.

Hiciste correctamente la solicitud para obtener un nuevo token de acceso y recibió la respuesta que tiene tu nuevo token de acceso . También estaba confundido por el hecho de que no recibí un nuevo token de actualización . Sin embargo, así es como debe ser, ya que puede usar el mismo token de actualización una y otra vez.

Creo que algunas de las otras respuestas dan por supuesto que querías obtener un nuevo token de actualización por algún motivo y sugirieron que vuelvas a autorizar al usuario, pero en realidad no es necesario, ya que el token de actualización que tienes funcionará hasta revocado por el usuario.

La respuesta de Rich Sutton finalmente funcionó para mí, después de darme cuenta de que la adición de access_type=offline se realiza en la solicitud del cliente front-end para un código de autorización, no en la solicitud back-end que intercambia ese código por access_token. Agregué un comentario a su respuesta y este enlace en Google para obtener más información sobre los tokens de actualización.

PD Si está utilizando Satellizer, aquí le mostramos cómo agregar esa opción a $ authProvider.google en AngularJS .

Establecer esto hará que el token de actualización se envíe siempre:

 $client->setApprovalPrompt('force'); 

un ejemplo se da a continuación (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'); 

Para mí estaba probando CalendarSampleServlet proporcionado por Google. Después de 1 hora, access_key agota el tiempo de espera y hay una redirección a una página de 401. Intenté todas las opciones anteriores, pero no funcionaron. Finalmente, al verificar el código fuente de ‘AbstractAuthorizationCodeServlet’ , pude ver que la redirección se deshabilitaría si las credenciales están presentes, pero idealmente debería haber revisado el refresh token!=null . Agregué el siguiente código a CalendarSampleServlet y funcionó después de eso. Gran alivio después de tantas horas de frustración. Gracias a Dios.

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

Para obtener refresh_token , debe incluir access_type=offline en la URL de solicitud de OAuth. Cuando un usuario se autentica por primera vez, recibirá un access_token que no es nulo, así como un access_token que caduca.

Si tiene una situación en la que un usuario puede volver a autenticar una cuenta para la cual ya tiene un token de autenticación (como las menciones de @SsjCosty), necesita recuperar la información de Google en la cuenta para la cual está el token. Para hacer eso, agrega un profile a tus ámbitos. Utilizando la gem OAuth2 Ruby, su solicitud final podría verse más o menos así:

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

Tenga en cuenta que el scope tiene dos entradas delimitadas por espacios, una para el acceso de solo lectura a Google Analytics, y la otra es solo el profile , que es un estándar de OpenID Connect.

Esto dará como resultado que Google proporcione un atributo adicional llamado id_token en la respuesta get_token . Para obtener información de id_token, consulte esta página en los documentos de Google. Hay un puñado de bibliotecas proporcionadas por Google que validarán y “descodificarán” esto para usted (utilicé la gem Ruby google-id-token ). Una vez que lo haya analizado, el subparámetro es efectivamente el ID único de la cuenta de Google.

Vale la pena señalar que, si cambia el scope, recibirá nuevamente un token de actualización para los usuarios que ya se hayan autenticado con el scope original. Esto es útil si, por ejemplo, ya tiene muchos usuarios y no desea que la aplicación desaparezca de Google.

Ah, y una nota final: no necesitas prompt=select_account , pero es útil si tienes una situación en la que tus usuarios quieran autenticarse con más de una cuenta de Google (es decir, no estás usando esto para iniciar sesión / autenticación).

ahora Google había rechazado esos parámetros en mi solicitud (access_type, prompt) … 🙁 y no hay ningún botón de “Revocar acceso”. Me siento frustrado por haber devuelto mi refresh_token lol

ACTUALIZACIÓN: Encontré la respuesta aquí: D puede recuperar el token de actualización mediante una solicitud https://developers.google.com/identity/protocols/OAuth2WebServer

curl -H “Tipo de contenido: application / x-www-form-urlencoded” \ https://accounts.google.com/o/oauth2/revoke?token= {token}

El token puede ser un token de acceso o un token de actualización. Si el token es un token de acceso y tiene un token de actualización correspondiente, el token de actualización también se revocará.

Si la revocación se procesa con éxito, el código de estado de la respuesta es 200. Para las condiciones de error, se devuelve un código de estado 400 junto con un código de error.

  #!/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 

Uso de acceso sin conexión y solicitud: el consentimiento me funcionó bien:

  auth2 = gapi.auth2.init({ client_id: '{cliend_id}' }); auth2.grantOfflineAccess({prompt:'consent'}).then(signInCallback); 

Mi solución fue un poco extraña … Probé todas las soluciones que encontré en Internet y nada. Sorprendentemente, esto funcionó: elimine las credenciales.json, actualice, vuelva a vincular su aplicación en su cuenta. El nuevo archivo credentials.json tendrá el token de actualización. Copia de seguridad de este archivo en alguna parte Luego sigue usando tu aplicación hasta que vuelva a aparecer el error de actualización del token. Elimine el archivo crendetials.json que ahora está solo con un mensaje de error (esto está en mi caso), luego pegue el archivo de credenciales anterior en la carpeta, ¡listo! Ha pasado 1 semana desde que hice esto y no tuve más problemas.