No recibo el token de actualización de Google OAuth

270

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
}
Muhammad Usman
fuente
He tenido un problema similar. Mira mi respuesta aquí
Arithran

Respuestas:

675

La refresh_tokenúnica se proporciona en la primera autorización del usuario. Las autorizaciones posteriores, como el tipo que realiza al probar una integración de OAuth2, no volverán a refresh_tokenaparecer. :)

  1. Vaya a la página que muestra las aplicaciones con acceso a su cuenta: https://myaccount.google.com/u/0/permissions .
  2. En el menú de aplicaciones de terceros, elija su aplicación.
  3. Haga clic en Eliminar acceso y luego haga clic en Aceptar para confirmar
  4. La próxima solicitud de OAuth2 que realice devolverá un refresh_token(siempre que también incluya el parámetro de consulta 'access_type = offline'.

Alternativamente, puede agregar los parámetros de consulta prompt=consent&access_type=offlinea la redirección de OAuth (consulte la página OAuth 2.0 de Google para aplicaciones de servidor web).

Esto hará que el usuario vuelva a autorizar la aplicación y siempre devolverá a refresh_token.

Rich Sutton
fuente
20
Esto no funcionó para mí, pero agregar el parámetro "access_type = offline" parecía ser el truco: developers.google.com/accounts/docs/OAuth2WebServer#offline
Jesse
87
Necesita access_type=offlineen todos los casos cuando desee el refresh_token.
DanH
55
Pero, ¿cómo actualizo el token después de su vencimiento en este caso?
vivek_jonam
55
@vivek_jonam Almacenar el token de actualización y la fecha de caducidad. Cuando caduca, solicita un nuevo token utilizando el token de actualización. Ver aquí: developers.google.com/accounts/docs/OAuth2WebServer#refresh
gelviis
44
Lo tengo trabajando con $client->setAccessType('offline'). El function setApprovalPrompt()ya está pasado force, por defecto.
Moey
57

Para obtener el token de actualización, debe agregar ambos approval_prompt=forcey, access_type="offline" si está utilizando el cliente java proporcionado 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");
Gal Morad
fuente
En el nodo: var authUrl = oauth2Client.generateAuthUrl ({tipo_acceso: 'fuera de línea', alcance: SCOPES, aprobación_prompt: 'fuerza'});
Joris Mans
2
Es indignante que google no haya abordado esto en su documentación o al menos no en la documentación de php o juramento2 que he estado mirando durante 7 horas. ¿Por qué en el mundo esto no está en un gran texto en negrita en sus documentos
Colin Rickels
¡Gracias! Los documentos aquí ( github.com/googlesamples/apps-script-oauth2 ) son muy engañosos acerca de este parámetro. Cuando agregué aprobacion_prompt = fuerza finalmente obtuve un token de actualización.
Alex Zhevzhik
28

Busqué una larga noche y esto está funcionando:

User-example.php modificado de admin-sdk

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

luego obtienes el código en la URL de redireccionamiento y la autenticación con el código y obtienes el token de actualización

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

Deberías almacenarlo ahora;)

Cuando su clave de acceso agota el tiempo de espera

$client->refreshToken($theRefreshTokenYouHadStored);
Norbert
fuente
Perfecto @ Norbert, esto era exactamente lo que necesitaba.
Emmanuel
¡Gracias! Respuesta exacta a mi pregunta @Norbert
varun teja-MVT
16

Esto me ha causado cierta confusión, así que pensé en compartir lo que aprendí de la manera difícil:

Cuando solicita acceso utilizando los parámetros access_type=offliney approval_prompt=force, debe recibir un token de acceso y un token de actualización . El token de acceso caduca poco después de recibirlo y deberá actualizarlo.

Realizó correctamente la solicitud para obtener un nuevo token de acceso y recibió la respuesta que tiene su 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 suponen que quería obtener un nuevo token de actualización por alguna razón y sugirió que vuelva a autorizar al usuario, pero en realidad no es necesario, ya que el token de actualización que tiene funcionará hasta revocado por el usuario.

jeteon
fuente
1
Tengo un CMS donde diferentes usuarios usan diferentes cuentas de Google para conectarse a la API de análisis. Sin embargo, a veces varios usuarios pueden conectarse usando la misma cuenta corporativa de google, pero cada uno quiere acceder a una cuenta de Analytics diferente. Solo el primero recibe el token de actualización, mientras que todos los demás no lo hacen y, por lo tanto, tienen que volver a conectarse cada hora. ¿No hay una manera de obtener el mismo token de actualización para las autenticaciones posteriores en lugar de solo el access_token que caduca en una hora?
SsjCosty
1
La API parece producir el token de actualización exactamente una vez. Cualquier "uso compartido" del token tendría que suceder en su código. Sin embargo, debe tener cuidado de no otorgar accidentalmente nuevos privilegios de acceso a los usuarios. Una manera simple de hacer esto es hacer que la aplicación realice un seguimiento de los tokens de actualización y las cuentas asociadas en su propio almacenamiento ('tabla' separada en SQLese). Luego, cuando desee obtener un nuevo token de acceso , verifique y use este posiblemente token común desde allí. Implementado de cierta manera, su código no necesita saber quién realmente obtuvo el token.
jeteon
1
No sé cómo podría identificar qué token de actualización debo asociar con un nuevo token de acceso que acabo de recibir. Hay diferentes usuarios que inician sesión, y lo único que tienen en común es que usan la misma cuenta de Google (correo electrónico) para conectarse a la API. Pero Google no envía una identificación de la cuenta o el correo electrónico, solo envía un token. Así que no sé cómo asociar los 2 usuarios diferentes de CMS ...
SsjCosty
He explicado completamente mi problema aquí: stackoverflow.com/questions/30217524/…
SsjCosty
Youtube oAuth2 refresh_token se muestra solo cuando se usa force.
Dmitry Polushkin
7

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

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

Zack Morris
fuente
Detalles muy menores pero importantes. Me salvó ! Gracias :)
Dexter
@ZackMorris Entonces ... ¿quieres decir que no puedo obtener refresh_token desde el backend usando el token de acceso?
Nunca más
@Nunca más No puedes obtener un refresh_token desde el access_token mismo. Si desea que su servidor maneje las actualizaciones, entonces deberá almacenar refresh_token en su base de datos la primera vez. Además, si está haciendo un flujo OAuth de cliente en el front-end, entonces los usuarios tendrán que enviar su refresh_token al back-end si quieren que el servidor los actualice.
Zack Morris el
4

Para obtenerlo refresh_token, debe incluirlo access_type=offlineen la URL de solicitud de OAuth. Cuando un usuario se autentica por primera vez, obtendrá un valor no válido refresh_tokeny uno access_tokenque caduca.

Si tiene una situación en la que un usuario puede volver a autenticar una cuenta para la que ya tiene un token de autenticación (como @SsjCosty menciona anteriormente), debe recuperar información de Google para qué cuenta es el token. Para hacer eso, agregue profilea sus ámbitos. Usando la gema OAuth2 Ruby, su solicitud final podría verse 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 alcance tiene dos entradas delimitadas por espacios, una para acceso de solo lectura a Google Analytics y la otra es justa profile, que es un estándar OpenID Connect.

Esto dará como resultado que Google proporcione un atributo adicional llamado id_tokenen la get_tokenrespuesta. 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 "decodificarán" esto por usted (utilicé la gema Ruby google-id-token ). Una vez que se analiza, el subparámetro es efectivamente el ID único de la cuenta de Google.

Vale la pena señalar que si cambia el alcance, volverá a recibir un token de actualización para los usuarios que ya se han autenticado con el alcance original. Esto es útil si, por ejemplo, ya tiene un grupo de usuarios y no desea que todos anulen la autenticación de la aplicación en Google.

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

coreyward
fuente
Creo que la parte sobre la identificación de usuarios sin almacenar información personal es clave. Gracias por señalarlo, no vi ninguna referencia en Google Docs sobre eso.
Danielo515
3

1. ¿Cómo obtener 'refresh_token'?

Solución: la opción access_type = 'offline' se debe usar al generar authURL. fuente: Uso de OAuth 2.0 para aplicaciones de servidor web

2. Pero incluso con 'access_type = offline', ¿no obtengo el 'refresh_token'?

Solución: tenga en cuenta que lo obtendrá solo en la primera solicitud, por lo que si lo está almacenando en algún lugar y existe una disposición para sobrescribir esto en su código cuando obtenga un nuevo access_token después de que caduque el anterior, asegúrese de no sobrescribir este valor.

De Google Auth Doc: (este valor = tipo_acceso)

Este valor indica al servidor de autorización de Google que devuelva un token de actualización y un token de acceso la primera vez que su aplicación intercambia un código de autorización por tokens.

Si necesita 'refresh_token' nuevamente, debe eliminar el acceso a su aplicación siguiendo los pasos escritos en la respuesta de Rich Sutton .

Mazahir Haroon
fuente
2

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

$client->setApprovalPrompt('force');

A continuación se muestra un ejemplo (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
fuente
1

Para mí estaba probando CalendarSampleServletproporcionado por Google. Después de 1 hora, se agota el tiempo de acceso y hay una redirección a una página 401. Probé 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 haberlo verificado refresh token!=null. Agregué el siguiente código CalendarSampleServlety 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;
}
Anoop Isaac
fuente
0

ahora google había rechazado esos parámetros en mi solicitud (access_type, prompt) ... :( y no hay ningún botón "Revocar acceso" en absoluto. Estoy frustrado por haber recuperado 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 condiciones de error, se devuelve un código de estado 400 junto con un código de error.

Tran Tien Duc
fuente
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
fuente
0

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); 
Carlos Eduardo Ki Lee
fuente
0

Mi solución fue un poco extraña ... probé todas las soluciones que encontré en Internet y nada. Sorprendentemente, esto funcionó: elimine credentials.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, siga usando su aplicación hasta que vuelva a aparecer el error del token de actualización. Elimine el archivo crendetials.json que ahora solo aparece con un mensaje de error (esto sucedió en mi caso), luego pegue su viejo archivo de credenciales en la carpeta, ¡listo! Ha pasado 1 semana desde que hice esto y no tuve más problemas.

Guilherme Canoa
fuente
0

Para obtener cada vez un nuevo refresh_token en la autenticación, el tipo de credenciales de OAuth 2.0 creadas en el panel debe ser "Otro". También, como se mencionó anteriormente, la opción access_type = 'offline' debe usarse al generar authURL.

Cuando utilice credenciales con el tipo "Aplicación web", no funcionará ninguna combinación de las variables prompt / license_prompt; de todos modos, obtendrá el refresh_token solo en la primera solicitud.

Martin Kask
fuente