Not receiving Google OAuth refresh token

2018-12-31 15:39发布

问题:

I want to get the access token from Google. The Google API says that to get the access token, send the code and other parameters to token generating page, and the response will be a JSON Object like :

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

However, I\'m not receiving the refresh token. The response in my case is:

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

回答1:

The refresh_token is only provided on the first authorization from the user. Subsequent authorizations, such as the kind you make while testing an OAuth2 integration, will not return the refresh_token again. :)

  1. Go to the page showing Apps with access to your account: https://myaccount.google.com/u/0/permissions.
  2. Under the Third-party apps menu, choose your app.
  3. Click Remove access and then click Ok to confirm
  4. The next OAuth2 request you make will return a refresh_token.

Alternatively, you can add the query parameter prompt=consent to the OAuth redirect (see Google\'s OAuth 2.0 for Web Server Applications page).

This will prompt the user to authorize the application again and will always return a refresh_token.



回答2:

In order to get the refresh token you have to add both approval_prompt=force and access_type=\"offline\" If you are using the java client provided by Google it will look like this:

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

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


回答3:

I searched a long night and this is doing the trick:

Modified user-example.php from admin-sdk

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

then you get the code at the redirect url and the authenticating with the code and getting the refresh token

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

You should store it now ;)

When your accesskey times out just do

$client->refreshToken($theRefreshTokenYouHadStored);


回答4:

This has caused me some confusion so I thought I\'d share what I\'ve come to learn the hard way:

When you request access using the access_type=offline and approval_prompt=force parameters you should receive both an access token and a refresh token. The access token expires soon after you receive it and you will need to refresh it.

You correctly made the request to get a new access token and received the response that has your new access token. I was also confused by the fact that I didn\'t get a new refresh token. However, this is how it is meant to be since you can use the same refresh token over and over again.

I think some of the other answers assume that you wanted to get yourself a new refresh token for some reason and sugggested that you re-authorize the user but in actual fact, you don\'t need to since the refresh token you have will work until revoked by the user.



回答5:

Rich Sutton\'s answer finally worked for me, after I realized that adding access_type=offline is done on the front end client\'s request for an authorization code, not the back end request that exchanges that code for an access_token. I\'ve added a comment to his answer and this link at Google for more info about refreshing tokens.

P.S. If you are using Satellizer, here is how to add that option to the $authProvider.google in AngularJS.



回答6:

Setting this will cause the refresh token to be sent every time:

$client->setApprovalPrompt(\'force\');

an example is given below (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\');


回答7:

For me I was trying out CalendarSampleServlet provided by Google. After 1 hour the access_key times out and there is a redirect to a 401 page. I tried all the above options but they didn\'t work. Finally upon checking the source code for \'AbstractAuthorizationCodeServlet\', I could see that redirection would be disabled if credentials are present, but ideally it should have checked for refresh token!=null. I added below code to CalendarSampleServlet and it worked after that. Great relief after so many hours of frustration . Thank God.

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


回答8:

In order to get the refresh_token you need to include access_type=offline in the OAuth request URL. When a user authenticates for the first time you will get back a non-nil refresh_token as well as an access_token that expires.

If you have a situation where a user might re-authenticate an account you already have an authentication token for (like @SsjCosty mentions above), you need to get back information from Google on which account the token is for. To do that, add profile to your scopes. Using the OAuth2 Ruby gem, your final request might look something like this:

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

Note the scope has two space-delimited entries, one for read-only access to Google Analytics, and the other is just profile, which is an OpenID Connect standard.

This will result in Google providing an additional attribute called id_token in the get_token response. To get information out of the id_token, check out this page in the Google docs. There are a handful of Google-provided libraries that will validate and “decode” this for you (I used the Ruby google-id-token gem). Once you get it parsed, the sub parameter is effectively the unique Google account ID.

Worth noting, if you change the scope, you\'ll get back a refresh token again for users that have already authenticated with the original scope. This is useful if, say, you have a bunch of users already and don\'t want to make them all un-auth the app in Google.

Oh, and one final note: you don\'t need prompt=select_account, but it\'s useful if you have a situation where your users might want to authenticate with more than one Google account (i.e., you\'re not using this for sign-in / authentication).



回答9:

now google had refused those parameters in my request (access_type, prompt)... :( and there is no \"Revoke Access\" button at all. I\'m frustrating because of getting back my refresh_token lol

UPDATE: I found the answer in here :D you can get back the refresh token by a request https://developers.google.com/identity/protocols/OAuth2WebServer

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

The token can be an access token or a refresh token. If the token is an access token and it has a corresponding refresh token, the refresh token will also be revoked.

If the revocation is successfully processed, then the status code of the response is 200. For error conditions, a status code 400 is returned along with an error code.



回答10:

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


回答11:

Using offline access and prompt:consent worked well to me:

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

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


回答12:

My solution was a bit weird..i tried every solution i found on internet and nothing. Surprisely this worked: delete the credentials.json, refresh, vinculate your app in your account again. The new credentials.json file will have the refresh token. Backup this file somewhere. Then keep using your app until the refresh token error comes again. Delete the crendetials.json file that now is only with an error message (this hapenned in my case), then paste you old credentials file in the folder, its done! Its been 1 week since ive done this and had no more problems.