Ne pas recevoir de jeton d'actualisation Google OAuth

270

Je souhaite obtenir le jeton d'accès de Google. L'API Google indique que pour obtenir le jeton d'accès, envoyez le code et d'autres paramètres à la page de génération de jeton, et la réponse sera un objet JSON comme:

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

Cependant, je ne reçois pas le jeton d'actualisation. La réponse dans mon cas est:

{
 "access_token" : "ya29.sddsdsdsdsds_h9v_nF0IR7XcwDK8XFB2EbvtxmgvB-4oZ8oU",
"token_type" : "Bearer",
"expires_in" : 3600
}
Muhammad Usman
la source
J'ai eu un problème similaire. Vérifiez ma réponse ici
Arithran

Réponses:

675

Le refresh_tokenn'est fourni qu'à la première autorisation de l'utilisateur. Les autorisations suivantes, telles que celles que vous effectuez lors du test d'une intégration OAuth2, ne renverront refresh_tokenplus. :)

  1. Accédez à la page affichant les applications ayant accès à votre compte: https://myaccount.google.com/u/0/permissions .
  2. Dans le menu Applications tierces, choisissez votre application.
  3. Cliquez sur Supprimer l'accès, puis sur OK pour confirmer
  4. La prochaine demande OAuth2 que vous ferez renverra un refresh_token(à condition qu'il comprenne également le paramètre de requête «access_type = offline».

Vous pouvez également ajouter les paramètres de requête prompt=consent&access_type=offlineà la redirection OAuth (voir la page OAuth 2.0 de Google pour les applications de serveur Web ).

Cela invitera l'utilisateur à autoriser à nouveau l'application et renverra toujours a refresh_token.

Rich Sutton
la source
20
Cela n'a pas fonctionné pour moi, mais l'ajout du paramètre "access_type = offline" a semblé faire l'affaire: developers.google.com/accounts/docs/OAuth2WebServer#offline
Jesse
87
Vous avez besoin access_type=offlinedans tous les cas quand vous le souhaitez refresh_token.
DanH
5
Mais comment puis-je actualiser le jeton après son expiration dans ce cas?
vivek_jonam
5
@vivek_jonam Stocke le jeton d'actualisation et la date d'expiration. Lorsqu'il expire, vous demandez un nouveau jeton à l'aide du jeton d'actualisation. Voir ici: developers.google.com/accounts/docs/OAuth2WebServer#refresh
gelviis
4
Je l'ai fait travailler avec $client->setAccessType('offline'). Le function setApprovalPrompt()est déjà transmis forcepar défaut.
moey
57

Pour obtenir le jeton d'actualisation, vous devez ajouter les deux approval_prompt=forceet access_type="offline" si vous utilisez le client java fourni par Google, il ressemblera à ceci:

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

AuthorizationCodeRequestUrl authorizationUrl =
            flow.newAuthorizationUrl().setRedirectUri(callBackUrl)
                    .setApprovalPrompt("force")
                    .setAccessType("offline");
Gal Morad
la source
Dans le nœud: var authUrl = oauth2Client.generateAuthUrl ({access_type: 'offline', portée: SCOPES, approbation_prompt: 'force'});
Joris Mans
2
Il est scandaleux que Google n'ait pas abordé ce sujet dans sa documentation ou du moins dans la documentation php ou oath2 que je regarde depuis 7 heures. Pourquoi dans le monde n'est-ce pas en gros caractères gras dans leurs documents
Colin Rickels
Je vous remercie! Les documents ici ( github.com/googlesamples/apps-script-oauth2 ) sont très trompeurs sur ce paramètre. Lorsque j'ai ajouté approbation_prompt = force, j'ai finalement obtenu un jeton d'actualisation.
Alex Zhevzhik
28

J'ai cherché une longue nuit et ça fait l'affaire:

User-example.php modifié depuis admin-sdk

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

puis vous obtenez le code à l'URL de redirection et l'authentification avec le code et obtenir le jeton d'actualisation

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

Vous devriez le stocker maintenant;)

Lorsque votre clé d'accès expire, faites

$client->refreshToken($theRefreshTokenYouHadStored);
Norbert
la source
Parfait @Norbert, c'était exactement ce dont j'avais besoin.
Emmanuel
Merci! Réponse exacte à ma question @Norbert
varun teja-MVT
16

Cela m'a causé une certaine confusion, alors j'ai pensé partager ce que je venais d'apprendre à la dure:

Lorsque vous demandez l'accès à l'aide des paramètres access_type=offlineet approval_prompt=force, vous devez recevoir à la fois un jeton d' accès et un jeton d' actualisation . L' accès jeton d' expire peu de temps après sa réception et vous devrez le rafraîchir.

Vous avez correctement fait la demande pour obtenir un nouveau jeton d' accès et reçu la réponse contenant votre nouveau jeton d' accès . J'ai également été troublé par le fait que je n'ai pas reçu de nouveau jeton d' actualisation . Cependant, c'est ainsi que cela doit être puisque vous pouvez utiliser le même rafraîchissement jeton d' encore et encore.

Je pense que certaines des autres réponses supposent que vous vouliez vous procurer un nouveau jeton d' actualisation pour une raison quelconque et ont suggéré que vous autorisiez à nouveau l'utilisateur, mais en fait, vous n'avez pas besoin de le faire puisque le jeton d' actualisation que vous avez fonctionnera jusqu'à révoqué par l'utilisateur.

jeteon
la source
1
J'ai un CMS où différents utilisateurs utilisent différents comptes Google pour se connecter à l'API d'analyse. Cependant, parfois, plusieurs utilisateurs peuvent se connecter en utilisant le même compte Google d'entreprise, mais chacun souhaitant accéder à un autre compte Analytics. Seul le premier reçoit le jeton d'actualisation, tandis que tous les autres ne le font pas et doivent donc se reconnecter toutes les heures. N'y a-t-il pas un moyen d'obtenir le même jeton d'actualisation pour les authentifications suivantes au lieu de simplement le access_token qui expire dans l'heure?
SsjCosty
1
L'API semble produire le jeton d' actualisation exactement une fois. Tout "partage" du jeton devrait avoir lieu dans votre code. Vous devez cependant faire attention à ne pas accorder accidentellement de nouveaux privilèges d'accès aux utilisateurs. Un moyen simple de le faire est de faire en sorte que l'application garde une trace des jetons d' actualisation et des comptes associés dans leur propre stockage («table» séparée dans SQLese). Ensuite, lorsque vous souhaitez obtenir un nouveau jeton d' accès, vous recherchez et utilisez ce jeton éventuellement commun à partir de là. Mis en œuvre d'une certaine manière, votre code n'a pas besoin de savoir qui a réellement obtenu le jeton.
jeteon
1
Je ne sais pas comment identifier le jeton d'actualisation à associer à un nouveau jeton d'accès que je viens de recevoir. Il existe différents utilisateurs qui se connectent, et la seule chose qu'ils ont en commun est qu'ils utilisent le même compte Google (e-mail) pour se connecter à l'API. Mais Google ne renvoie pas l'ID du compte ou de l'e-mail, il renvoie simplement un jeton. Je ne sais donc pas comment associer les 2 utilisateurs CMS différents ...
SsjCosty
J'ai expliqué en détail mon problème ici: stackoverflow.com/questions/30217524/…
SsjCosty
Youtube oAuth2 refresh_token affiché uniquement en cas de force.
Dmitry Polushkin
7

La réponse de Rich Sutton a finalement fonctionné pour moi, après avoir réalisé que l'ajout access_type=offlineest effectué sur la demande du client frontal pour un code d'autorisation, pas sur la demande principale qui échange ce code pour un access_token. J'ai ajouté un commentaire à sa réponse et ce lien sur Google pour plus d'informations sur les jetons d'actualisation.

PS Si vous utilisez Satellizer, voici comment ajouter cette option au $ authProvider.google dans AngularJS .

Zack Morris
la source
Détails très mineurs mais importants. M'a sauvé ! Merci :)
Dexter
@ZackMorris Alors .. voulez-vous dire que je ne peux pas obtenir refresh_token du backend en utilisant un jeton d'accès?
Nevermore
@Nevermore Vous ne pouvez pas obtenir un refresh_token du access_token lui-même. Si vous souhaitez que votre serveur gère les actualisations, vous devrez stocker le refresh_token dans votre base de données la première fois. De plus, si vous effectuez un flux OAuth client sur le front-end, les utilisateurs devront envoyer leur refresh_token au back-end s'ils souhaitent que le serveur se rafraîchisse pour eux.
Zack Morris
4

Afin d'obtenir le, refresh_tokenvous devez inclure access_type=offlinedans l'URL de demande OAuth. Lorsqu'un utilisateur s'authentifie pour la première fois, vous récupérerez un non nul refresh_tokenainsi access_tokenqu'un expirant.

Si vous avez une situation où un utilisateur peut réauthentifier un compte pour lequel vous avez déjà un jeton d'authentification (comme @SsjCosty le mentionne ci-dessus), vous devez récupérer les informations de Google sur le compte auquel le jeton est destiné. Pour ce faire, ajoutez profileà vos étendues. En utilisant la gemme OAuth2 Ruby, votre demande finale pourrait ressembler à ceci:

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

Notez que la portée a deux entrées délimitées par des espaces, une pour l'accès en lecture seule à Google Analytics, et l'autre est juste profile, qui est une norme OpenID Connect.

Cela se traduira par Google fournissant un attribut supplémentaire appelé id_tokendans la get_tokenréponse. Pour obtenir des informations sur id_token, consultez cette page dans la documentation Google. Il existe une poignée de bibliothèques fournies par Google qui valideront et «décoderont» ceci pour vous (j'ai utilisé la gemme Ruby google-id-token ). Une fois que vous l'avez analysé, lesub paramètre est effectivement l'ID de compte Google unique.

À noter, si vous changez la portée, vous récupérerez à nouveau un jeton d'actualisation pour les utilisateurs qui se sont déjà authentifiés avec la portée d'origine. Cela est utile si, par exemple, vous avez déjà un groupe d'utilisateurs et que vous ne souhaitez pas tous les annuler l'authentification de l'application dans Google.

Oh, et une dernière remarque: vous n'en avez pas besoin prompt=select_account , mais c'est utile si vous avez une situation où vos utilisateurs peuvent vouloir s'authentifier avec plus d'un compte Google (c'est-à-dire que vous ne l'utilisez pas pour la connexion / l'authentification) .

coreyward
la source
Je pense que la partie sur l'identification des utilisateurs sans stocker aucune information personnelle est la clé. Merci de l'avoir signalé, je n'ai vu aucune référence sur Google Documents à ce sujet.
Danielo515
3

1. Comment obtenir 'refresh_token'?

Solution: l' option access_type = 'offline' doit être utilisée lors de la génération d'authURL. source: Utilisation d'OAuth 2.0 pour les applications de serveur Web

2. Mais même avec 'access_type = offline', je ne reçois pas le 'refresh_token'?

Solution: veuillez noter que vous ne l'obtiendrez qu'à la première demande, donc si vous le stockez quelque part et qu'il existe une disposition pour l'écraser dans votre code lorsque vous obtenez un nouveau access_token après l'expiration précédente, assurez-vous de ne pas écraser cette valeur.

Depuis Google Auth Doc: (cette valeur = access_type)

Cette valeur indique au serveur d'autorisation Google de renvoyer un jeton d'actualisation et un jeton d'accès la première fois que votre application échange un code d'autorisation contre des jetons.

Si vous avez à nouveau besoin de 'refresh_token', vous devez supprimer l'accès à votre application en suivant les étapes écrites dans la réponse de Rich Sutton .

Mazahir Haroon
la source
2

Si vous le définissez, le jeton d'actualisation sera envoyé à chaque fois:

$client->setApprovalPrompt('force');

un exemple est donné ci-dessous (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
la source
1

Pour moi, j'essayais CalendarSampleServletfourni par Google. Après 1 heure, access_key expire et il y a une redirection vers une page 401. J'ai essayé toutes les options ci-dessus mais elles n'ont pas fonctionné. Enfin, en vérifiant le code source de «AbstractAuthorizationCodeServlet» , j'ai pu voir que la redirection serait désactivée si les informations d'identification sont présentes, mais idéalement, il aurait dû vérifier refresh token!=null. J'ai ajouté le code ci-dessous à CalendarSampleServletet cela a fonctionné après cela. Grand soulagement après tant d'heures de frustration. Dieu merci.

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

maintenant, google avait refusé ces paramètres dans ma demande (access_type, prompt) ... :( et il n'y a pas de bouton "Révoquer l'accès" du tout. Je suis frustré à cause du retour de mon refresh_token lol

MISE À JOUR: J'ai trouvé la réponse ici: D vous pouvez récupérer le jeton d'actualisation par une demande https://developers.google.com/identity/protocols/OAuth2WebServer

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

Le jeton peut être un jeton d'accès ou un jeton d'actualisation. Si le jeton est un jeton d'accès et qu'il a un jeton d'actualisation correspondant, le jeton d'actualisation sera également révoqué.

Si la révocation est traitée avec succès, le code d'état de la réponse est 200. Pour les conditions d'erreur, un code d'état 400 est renvoyé avec un code d'erreur.

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

Utilisation de l' accès hors ligne et de l' invite: le consentement m'a bien fonctionné:

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

   auth2.grantOfflineAccess({prompt:'consent'}).then(signInCallback); 
Carlos Eduardo Ki Lee
la source
0

Ma solution était un peu bizarre ... j'ai essayé toutes les solutions que j'ai trouvées sur Internet et rien. Étonnamment, cela a fonctionné: supprimez les informations d'identification, actualisez, vinculez à nouveau votre application dans votre compte. Le nouveau fichier credentials.json aura le jeton d'actualisation. Sauvegardez ce fichier quelque part. Ensuite, continuez à utiliser votre application jusqu'à ce que l'erreur d'actualisation du jeton réapparaisse. Supprimez le fichier crendetials.json qui n'est maintenant qu'avec un message d'erreur (cela s'est produit dans mon cas), puis collez-vous l'ancien fichier d'informations d'identification dans le dossier, c'est fait! Cela fait 1 semaine depuis que j'ai fait ça et je n'ai eu plus de problèmes.

Guilherme Canoa
la source
0

Afin d'obtenir un nouveau refresh_token à chaque authentification, le type d'informations d'identification OAuth 2.0 créées dans le tableau de bord doit être "Autre". De même, comme mentionné ci-dessus, l'option access_type = 'offline' doit être utilisée lors de la génération de l'authURL.

Lorsque vous utilisez des informations d'identification avec le type "Application Web", aucune combinaison de variables prompt / approbation_prompt ne fonctionnera - vous n'obtiendrez toujours le refresh_token qu'à la première demande.

Martin Kask
la source