未收到Google OAuth刷新令牌

时间:2012-05-31 05:30:07

标签: gdata gdata-api access-token

我想从Google获取访问令牌。 The Google API says获取访问令牌,将代码和其他参数发送到令牌生成页面,响应将是一个JSON对象,如:

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

但是,我没有收到刷新令牌。我的回答是:

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

17 个答案:

答案 0 :(得分:597)

refresh_token仅在用户的第一次授权时提供。后续授权(例如您在测试OAuth2集成时所使用的类型)将不会再次返回refresh_token。 :)

  1. 转到显示可访问您帐户的应用的页面: https://myaccount.google.com/u/0/permissions
  2. 在第三方应用菜单下,选择您的应用。
  3. 单击“删除访问权限”,然后单击“确定”以确认
  4. 您发出的下一个OAuth2请求将返回refresh_token
  5. 或者,您可以将查询参数prompt=consent添加到OAuth重定向(请参阅Google的OAuth 2.0 for Web Server Applications页面)。

    这将提示用户再次授权该应用程序,并始终返回refresh_token

答案 1 :(得分:56)

要获取刷新令牌,您必须同时添加approval_prompt=forceaccess_type="offline" 如果您使用的是Google提供的Java客户端,它将如下所示:

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

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

答案 2 :(得分:27)

我搜索了一个漫长的夜晚,这就是诀窍:

来自admin-sdk的修改后的user-example.php

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

然后您将获得重定向网址的代码 并使用代码进行身份验证并获取刷新令牌

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

你现在应该存储它;)

当你的访问键超时时,只需执行

$client->refreshToken($theRefreshTokenYouHadStored);

答案 3 :(得分:14)

这给我带来了一些困惑,所以我想我会分享我学到的东西:

当您使用access_type=offlineapproval_prompt=force参数请求访问权限时,您应该会收到访问令牌和刷新令牌。 访问令牌在您收到后很快就会过期,您需要刷新它。

您正确地提出了获取新访问令牌的请求,并收到了包含新访问令牌的回复。我也因为没有获得新的刷新令牌而感到困惑。但是,这就是它的意思,因为你可以一遍又一遍地使用相同的刷新标记。

我认为其他一些答案假设您想出于某种原因让自己获得一个新的刷新令牌,并建议您重新授权用户,但事实上,您不需要自从刷新令牌以来,您将一直工作,直到被用户撤消。

答案 4 :(得分:7)

Rich Sutton's answer在我意识到在前端客户端的授权代码请求上添加access_type=offline之后终于为我工作了,后端请求,用于交换access_token的代码。我已经在他的回答和this link at Google中添加了评论,以获取有关刷新令牌的更多信息。

P.S。如果您使用的是卫星,here is how to add that option to the $authProvider.google in AngularJS

答案 5 :(得分:4)

我想为遇到此问题的那些沮丧的灵魂添加有关此主题的更多信息。为离线应用获取刷新令牌的关键是确保您正在显示同意屏幕refresh_token 仅在用户通过单击“允许”授予授权后立即返回。

enter image description here

在我在开发环境中进行了一些测试并因此已经在给定帐户上授权了我的应用程序之后,我(我怀疑还有很多其他人)遇到了这个问题。然后我转到生产并尝试使用已经授权的帐户再次进行身份验证。在这种情况下,同意屏幕不会再次出现并且 api 不会返回新的刷新令牌。要使此工作正常进行,您必须通过以下任一方式强制同意屏幕再次出现:

prompt=consent

approval_prompt=force

任一个都行,但您不应同时使用两者。 从 2021 年开始,我建议使用 prompt=consent,因为它替换了旧参数 approval_prompt,并且在某些 api 版本中,后者实际上已损坏 (https://github.com/googleapis/oauth2client/issues/453) .此外,prompt 是一个以空格分隔的列表,因此您可以将其设置为 prompt=select_account%20consent,如果您想要两者。

当然你还需要:

access_type=offline

补充阅读:

答案 6 :(得分:2)

设置此项将导致每次都发送刷新令牌:

$client->setApprovalPrompt('force');

下面给出一个例子(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 :(得分:2)

要获得refresh_token,您需要在OAuth请求网址中加入access_type=offline。当用户第一次进行身份验证时,您将返回非零refresh_token以及过期的access_token

如果您的用户可能会重新验证您已拥有身份验证令牌的帐户(例如上面提到的@SsjCosty),您需要从Google获取该令牌所针对的帐户的信息。为此,请将profile添加到您的范围。使用OAuth2 Ruby gem,您的最终请求可能如下所示:

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

请注意,示波器有两个以空格分隔的条目,一个用于对Google Analytics的只读访问,另一个只有profile,这是一个OpenID Connect标准。

这会导致Google在id_token响应中提供名为get_token的附加属性。要从Google文档中的id_token,check out this page中获取信息。有一些谷歌提供的库将为您验证和“解码”(我使用了Ruby google-id-token gem)。解析后,sub参数实际上是唯一的Google帐户ID。

值得注意的是,如果你更改范围,那么对于已经使用原始范围进行身份验证的用户,您将再次获得刷新令牌。如果您已经有很多用户并且不想让他们全部取消Google中的应用程序,那么这非常有用。

哦,还有一个最后的注意事项:您不需要 prompt=select_account,但如果您的用户可能希望使用多个Google帐户进行身份验证,则此功能非常有用(即,您没有使用它进行登录/身份验证。)

答案 8 :(得分:2)

1。。如何获取“ refresh_token”?

解决方案:生成authURL时应使用access_type ='offline'选项。 来源:Using OAuth 2.0 for Web Server Applications

2。但即使使用'access_type = offline',我也无法获得'refresh_token'吗?

解决方案::请注意,您只会在第一个请求时得到它,因此,如果将其存储在某个地方,并且有一项规定可以在以前的到期之后获取新的access_token时在代码中覆盖它,然后确保不要覆盖此值。

来自Google身份验证文档:(此值= access_type)

  

此值指示Google授权服务器返回一个   首次在您的应用程序中刷新令牌和访问令牌   交换令牌的授权码。

如果再次需要“ refresh_token”,则需要按照Rich Sutton's answer中编写的步骤删除对应用程序的访问权限。

答案 9 :(得分:1)

我正在使用nodejs客户端访问私有数据

解决方案是在 oAuth2Client.generateAuthUrl 函数中的设置对象中添加值为同意 promp 属性。 这是我的代码:

const getNewToken = (oAuth2Client, callback) => {
    const authUrl = oAuth2Client.generateAuthUrl({
        access_type: 'offline',
        prompt: 'consent',
        scope: SCOPES,
    })
    console.log('Authorize this app by visiting this url:', authUrl)
    const rl = readline.createInterface({
        input: process.stdin,
        output: process.stdout,
    })
    rl.question('Enter the code from that page here: ', (code) => {
        rl.close()
        oAuth2Client.getToken(code, (err, token) => {
            if (err) return console.error('Error while trying to retrieve access token', err)
            oAuth2Client.setCredentials(token)
            // Store the token to disk for later program executions
            fs.writeFile(TOKEN_PATH, JSON.stringify(token), (err) => {
                if (err) return console.error(err)
                console.log('Token stored to', TOKEN_PATH)
            })
            callback(oAuth2Client)
        })
    })
}

您可以使用在线参数提取器获取用于生成令牌的代码:

Online parameters extractor

这是来自Google官方文档的完整代码:

https://developers.google.com/sheets/api/quickstart/nodejs

我希望这些信息有用

答案 10 :(得分:1)

对我而言,我正在试用Google提供的CalendarSampleServlet。 1小时后, access_key 超时,并且重定向到401页面。我尝试了以上所有选项,但它们没有用。最后,在检查'AbstractAuthorizationCodeServlet'的源代码后,我可以看到如果存在凭证,将禁用重定向,但理想情况下应该检查refresh token!=null。我将下面的代码添加到CalendarSampleServlet,然后它就可以了。经过这么多小时的挫折后,我感到很欣慰。感谢上帝。

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

答案 11 :(得分:0)

现在google已经拒绝了我的请求中的那些参数(access_type,提示)...... :(并且根本没有“撤销访问”按钮。因为取回我的refresh_token lol我很沮丧

更新: 我在这里找到了答案:D您可以通过请求获取刷新令牌 https://developers.google.com/identity/protocols/OAuth2WebServer

  

卷曲-H“内容类型:application / x-www-form-urlencoded”\           https://accounts.google.com/o/oauth2/revoke?token= {令牌}

     

令牌可以是访问令牌或刷新令牌。如果令牌是访问令牌并且它具有相应的刷新令牌,则刷新令牌也将被撤销。

     

如果成功处理了吊销,则响应的状态代码为200.对于错误条件,将返回状态代码400以及错误代码。

答案 12 :(得分: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

答案 13 :(得分:0)

使用离线访问提示:同意对我有用:

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

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

答案 14 :(得分:0)

我的解决方案有点奇怪..我尝试了我在互联网上找到的所有解决方案,没有。令人惊讶的是,这有效:删除credentials.json,刷新,再次在您的帐户中vinculate您的应用程序。新的credentials.json文件将具有刷新令牌。在某处备份此文件。 然后继续使用您的应用程序,直到再次出现刷新令牌错误。删除现在只有错误消息的crendetials.json文件(在我的情况下这是hapenned),然后将旧的凭证文件粘贴到文件夹中,完成! 自从我做完这一周以来已经过去了一周,没有更多问题。

答案 15 :(得分:0)

为了在每次认证时获取新的refresh_token,在仪表板上创建的OAuth 2.0凭据的类型应为“其他”。同样如上所述,在生成authURL时应使用access_type ='offline'选项。

当使用类型为“ Web应用程序”的凭据时,提示/ approval_prompt变量的组合将不起作用-您仍将仅在第一个请求时获得refresh_token。

答案 16 :(得分:0)

access_type=offline添加到授权Google授权URL中对我来说很成功。我正在使用Java和Spring框架。

这是创建客户注册的代码:

return CommonOAuth2Provider.GOOGLE
                    .getBuilder(client)
                    .scope("openid", "profile", "email", "https://www.googleapis.com/auth/gmail.send")
                    .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
                    .authorizationUri("https://accounts.google.com/o/oauth2/v2/auth?access_type=offline")
                    .clientId(clientId)
                    .redirectUriTemplate("{baseUrl}/{action}/oauth2/code/{registrationId}")
                    .clientSecret(clientSecret)
                    .build();

这里的重要部分是授权URI,附加了?access_type=offline