未收到Google OAuth刷新令牌

389
我希望从Google那里获取访问令牌。 Google API 表示,要获取访问令牌,需要将代码和其他参数发送到令牌生成页面,响应将是一个类似于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
}

1
我曾经遇到过类似的问题。请查看我的回答这里 - Arithran
20个回答

957
refresh_token只在用户首次授权时提供。随后的授权(例如测试OAuth2集成时进行的授权)将不会再返回refresh_token
操作步骤如下:
  1. 进入显示已访问您的帐户的应用程序页面: https://myaccount.google.com/u/0/permissions
  2. 在“第三方应用程序”菜单下,选择您的应用程序。
  3. 点击“移除访问权限”,然后点击“确认”。
  4. 下一次进行OAuth2请求时,将返回一个refresh_token(前提是它还包括“access_type=offline”查询参数)。
另外,您可以在OAuth重定向中添加查询参数prompt=consent&access_type=offline(请参阅Google的OAuth 2.0 for Web Server Applications页面)。
这将提示用户再次授权该应用程序,并始终返回一个refresh_token

25
这对我没有用,但添加参数 "access_type=offline" 似乎解决了问题:https://developers.google.com/accounts/docs/OAuth2WebServer#offline - Jesse
98
当您需要refresh_token时,所有情况下都需要使用access_type=offline - DanH
8
在这种情况下,我该如何在令牌过期后刷新令牌? - vivek_jonam
6
存储刷新令牌和到期日期。当它过期时,使用刷新令牌请求新的令牌。请参见此处:https://developers.google.com/accounts/docs/OAuth2WebServer#refresh - gelviis
4
如果你无法使其工作,请记住access_type=offline是前端选项,当客户端请求授权码时传递的选项,而不是后端选项,用于在服务器交换该代码以获取access_token。用户通过授予离线访问权限向服务器授予了无限期刷新令牌的权限。这里提供更多信息和可在curl中尝试的URL:https://developers.google.com/identity/protocols/OAuth2WebServer - Zack Morris
显示剩余19条评论

72
为了获取刷新令牌,您必须添加 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");

1
在 Node 中:var authUrl = oauth2Client.generateAuthUrl({ access_type: 'offline', scope: SCOPES, approval_prompt:'force' }); - Joris Mans
5
谷歌不在他们的文档中解决这个问题,或者至少不在我已经盯了7小时的PHP或Oath2文档中解决,这太令人气愤了。为什么这不是在他们的文档中以大号粗体字显示? - Colin Rickels
谢谢!这里的文档(https://github.com/googlesamples/apps-script-oauth2)对于这个参数非常误导。当我添加了approval_prompt=force后,我终于得到了一个刷新令牌。 - Alex Zhevzhik
8
强制批准(approval_prompt=force)对我无效,但是询问用户(prompt=consent)可以。 - Stefan Reich

58

对于那些遇到这个问题的心灵疲惫的人,我想为这个主题增加一些信息。获取离线应用程序的刷新令牌的关键是确保您正在呈现同意屏幕。只有在用户通过单击“允许”授权后,refresh_token才会立即返回。

输入图像描述

这个问题对我(以及许多其他人)来说是在我在开发环境中进行了一些测试之后出现的,因此我已经在给定的帐户上授权了我的应用程序。然后,我转移到生产并尝试再次使用已经被授权的帐户进行身份验证。在这种情况下,同意屏幕将不会再次出现,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

更多阅读:


如果你正在使用GoLang oauth库,你可以通过oauth2.ApprovalForce AuthCodeOption来设置这个:https://pkg.go.dev/golang.org/x/oauth2#AuthCodeOption - Aaron Krauss
这对我来说是关键 - undefined

33

我搜索了一个漫长的夜晚,这个可以解决问题:

从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);

完美的@Norbert,这正是我所需要的。 - Emmanuel
谢谢!@Norbert 给了我完美的答案。 - varun teja-M.V.T
你解决了我的生活。 - jimi-del

21
这让我有些困惑,所以我想分享一下我通过艰苦学习得出的结论:
当您使用 access_type=offline 和 approval_prompt=force 参数请求访问权限时,您应该收到一个访问令牌和一个刷新令牌。访问令牌在您收到它后不久就会过期,您需要刷新它。
您正确地发出了请求以获取新的访问令牌,并收到了包含新访问令牌的响应。我也被事实困惑了:我没有得到新的刷新令牌。然而,这就是它的意思,因为您可以一遍又一遍地使用同一个刷新令牌。
我认为其他答案假设你想出于某种原因获取自己的新刷新令牌,并建议您重新授权用户,但实际上,您不需要这样做,因为您拥有的刷新令牌将一直有效,直到被用户撤销。

1
我有一个CMS,不同的用户使用不同的Google帐户连接到分析API。然而,有时候几个用户可以使用相同的企业Google帐户连接,但每个人都想访问不同的分析帐户。只有第一个人收到刷新令牌,而其他所有人都没有,因此必须每小时重新连接。难道没有办法在后续身份验证中获取相同的刷新令牌,而不仅仅是在一小时内过期的访问令牌吗? - Costin_T
1
API似乎只产生一次刷新令牌。任何令牌的“共享”都必须在您的代码中完成。但是,您必须小心,不要意外地为用户提供新的访问权限。一个简单的方法是让应用程序在其自己的存储(SQLese中的单独“表”)中跟踪刷新令牌和相关联的帐户。然后,当您想要获取新的访问令牌时,您可以从其中检查并使用这个可能通用的令牌。以特定的方式实现,您的代码不需要知道谁实际上获得了令牌。 - jeteon
1
我不知道如何确定应该将哪个刷新令牌与我刚刚获得的新访问令牌关联起来。有不同的用户进行登录,他们唯一共同的是使用相同的Google帐户(电子邮件)连接到API。但是Google不会发送帐户或电子邮件的ID,它只会发送一个令牌。因此,我不知道如何关联这2个不同的CMS用户... - Costin_T
我在这里完全解释了我的问题:https://dev59.com/Kl0a5IYBdhLWcg3wipJA - Costin_T
只有在使用force参数时,才会显示Youtube的oAuth2 refresh_token。 - Dmitry Polushkin

9
Rich Sutton的答案最终对我起作用了,在我意识到在前端客户端请求授权码时添加access_type=offline,而不是后端请求该代码以交换访问令牌时。我已在他的回答中添加了评论,并提供此链接以获取有关刷新令牌的更多信息。

P.S. 如果您使用Satellizer,在AngularJS中将该选项添加到$authProvider.google的方法可以参考这里


非常微小的细节,但却非常重要。救了我一命!谢谢 :) - Dexter
@ZackMorris 那么..你的意思是说我不能使用访问令牌从后端获取刷新令牌吗? - Nevermore
@Nevermore 你无法从 access_token 中获取 refresh_token。如果你想让服务器处理刷新,那么你需要在第一次存储 refresh_token 到你的数据库中。此外,如果你正在前端进行客户端 OAuth 流程,则用户必须将他们的 refresh_token 发送到后端,如果他们希望服务器为他们刷新。 - Zack Morris

7
为了获取refresh_token,您需要在OAuth请求URL中包含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 在 get_token 响应中提供一个名为 id_token 的附加属性。要从 id_token 中获取信息,请参阅 Google 文档中的 此页面。有一些由 Google 提供的库可以验证和“解码”它(我使用 Ruby 的 google-id-token gem)。一旦解析它,sub 参数就是唯一的 Google 帐户 ID。
值得注意的是,如果您更改了范围,那么已经使用原始范围进行身份验证的用户将再次收到刷新令牌。如果您已经有很多用户,并且不想让他们全部取消 Google 应用的授权,则此功能非常有用。

哦,最后要注意的一点是:你不必须prompt=select_account,但如果你的用户可能想要使用多个Google账户进行身份验证(即,你不是用它进行登录/身份验证),那么这将非常有用。


我认为关于在不存储任何个人信息的情况下识别用户的部分非常关键。谢谢你指出来,我在谷歌文档中没有看到任何相关的参考资料。 - Danielo515

3

1. 如何获取 'refresh_token'?

解决方案: 在生成authURL时应该使用access_type='offline'选项。 来源:使用OAuth 2.0进行Web服务器应用程序开发

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

解决方案:请注意,您只会在第一次请求时获得它。因此,如果您在某个地方存储它,并且在获取先前过期后的新访问令牌时有覆盖此值的代码,请确保不要覆盖此值。

来自Google Auth文档:(此值=access_type)

该值指示Google授权服务器在您的应用程序首次交换授权码以获取令牌时返回刷新令牌和访问令牌。

如果您需要再次获得'refresh_token',则需要按照Rich Sutton的回答中的步骤删除您的应用程序的访问权限。


3
我正在使用Node.js客户端访问私人数据。
解决方案是在oAuth2Client.generateAuthUrl函数的设置对象中添加具有值“consent”的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)
        })
    })
}

你可以使用在线参数提取器获取生成令牌的代码: 在线参数提取器 这是来自Google官方文档的完整代码:

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

我希望这些信息有用。


这似乎是正确的方式。如果用户从Google帐户设置中撤销了访问权限,并且您调用了“oAuth2Client.revokeToken”(如果您的应用程序支持“取消链接”),则此方法将完美运行。 - arjavlad

2
使用Postman获取刷新令牌的示例配置如下:

enter image description here

预期回应

enter image description here


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接