ChatGPT解决这个技术问题 Extra ChatGPT

invalid_grant 试图从谷歌获取 oAuth 令牌

在尝试从 Google 获取 oAuth 令牌以连接到他们的联系人 API 时,我不断收到 invalid_grant 错误。所有的信息都是正确的,我已经检查了三次,这让我很困惑。

有谁知道可能导致此问题的原因?我尝试为其设置不同的客户端 ID,但得到相同的结果,我尝试连接许多不同的方式,包括尝试强制身份验证,但结果仍然相同。

对我来说,问题出在谷歌凭据页面上......我创建了另一个......并解决了问题......
我最近遇到了这个问题,当您尝试再次使用刷新令牌请求第一个访问令牌时也会发生这种情况,然后 invalid_grant 也出现了。我通过重置秘密然后在第一个请求中插入第一个令牌来解决它。如果我失败并再次尝试,我将再次获得 invalid_grant .. 第一次您需要捕获令牌保存,然后仅使用刷新令牌刷新,否则您“无效”
@Sagan 愿意再次解释您的流程和顺序吗?
@MikeyB 有点晚了,但可以肯定的是,如果您由于和我一样的原因而收到无效的授权错误,那么您可以通过在谷歌控制台中重置密钥并使用新的密钥再次完成令牌生成流程来解决它,但不要忘记保存只生成一次的刷新令牌,然后您可以使用刷新令牌生成访问令牌。
@Sagan,但是在接下来的 7 天内,当它再次被撤销时,您将再次这样做。是否有永久或更持久的方法来做到这一点?

l
laander

虽然这是一个老问题,但似乎很多人仍然遇到它 - 我们花了几天时间自己追踪这个问题。

在 OAuth2 规范中,“invalid_grant”是与无效/过期/撤销令牌(授权授权或刷新令牌)相关的所有错误的统称。

对我们来说,问题有两个方面:

用户主动撤销了对我们应用程序的访问权限是有道理的,但请注意:撤销后 12 小时,Google 停止在其响应中发送错误消息:“error_description”:“令牌已被撤销。”这相当具有误导性,因为您会假设错误消息始终存在,但事实并非如此。您可以在应用权限页面查看您的应用是否仍有访问权限。用户已重置/恢复其 Google 密码 2015 年 12 月,Google 更改了其默认行为,以便非 Google Apps 用户的密码重置将自动撤销所有用户的应用程序刷新令牌。撤销时,错误消息遵循与前例相同的规则,因此您只会在前 12 小时内获得“error_description”。似乎没有任何方法可以知道用户是手动撤销访问(有意)还是由于密码重置(副作用)而发生的。

除此之外,还有无数其他可能触发错误的潜在原因:

服务器时钟/时间不同步 未授权离线访问 受 Google 限制 使用过期的刷新令牌 用户已闲置 6 个月 使用服务工作人员电子邮件而不是客户端 ID 短时间内访问令牌过多 客户端 SDK 可能已过时 不正确/不完整刷新令牌

I've written a short article 总结每个项目并提供一些调试指南,以帮助找到罪魁祸首。希望能帮助到你。


另一种情况是,如果您尝试从相同的身份验证代码中多次获取令牌。
这正是我的问题,只是意外地撤销了该应用程序。然后必须通过终端重新运行 refreshToken.php 以生成另一个授权码,然后在各处替换该 clientID 的 refreshToken。
就我而言,我一个月前更改了密码。我发现我需要再次授予应用程序权限并获取新的刷新令牌
a
aroth

尽管根据 bonkydog 的回答在我的请求中指定了“离线”access_type,但我遇到了同样的问题。长话短说,我发现这里描述的解决方案对我有用:

https://groups.google.com/forum/#!topic/google-analytics-data-export-api/4uNaJtquxCs

本质上,当您在 Google API 的控制台中添加 OAuth2 客户端时,Google 会给您一个“客户端 ID”和一个“电子邮件地址”(假设您选择“webapp”作为您的客户端类型)。尽管 Google 的命名约定具有误导性,但他们希望您在访问其 OAuth2 API 时将“电子邮件地址”作为 client_id 参数的值发送。

这适用于调用这两个 URL 时:

https://accounts.google.com/o/oauth2/auth

https://accounts.google.com/o/oauth2/token

请注意,如果您使用“客户 ID”而不是“电子邮件地址”调用第一个 URL,则调用该 URL 将成功。但是,当尝试从第二个 URL 获取不记名令牌时,使用从该请求返回的代码将不起作用。相反,您将收到“错误 400”和“invalid_grant”消息。


绝对荒谬:如果您获得初始刷新令牌,尤其是它与 client_id 一起使用的部分。谷歌的 API 和他们的文档是一团糟。
这么多小时,我一直在努力解决这个问题。我从没想过“client_id”不是“client_id”字段所期望的。除了偶尔获得 refresh_token 并且它确实有效的时候。很确定我目前对谷歌的说法不能在 SO 上说。
您好.. 我找不到你们所说的“电子邮件”地址。这就是我在控制台中所拥有的 --> pbs.twimg.com/media/CVVcEBWUwAAIiCy.png:large
该电子邮件地址在哪里?我有同样的问题
client_id 替换为用户的电子邮件会产生 invalid_client 错误。这个答案似乎已经过时了。
b
bonkydog

当我在将用户发送到 OAuth 时没有明确请求“离线”访问时遇到了这个问题“你想授予这个应用程序触摸你的东西的权限吗?”页。

确保在请求中指定 access_type=offline。

此处的详细信息:https://developers.google.com/accounts/docs/OAuth2WebServer#offline

(另外:我认为 Google 在 2011 年末添加了此限制。如果您有在那之前的旧令牌,您需要将您的用户发送到权限页面以授权离线使用。)


@Adders 我同意。我将 access_type 设置为 offline,此错误仍然发生。
浏览此 developers.google.com/android-publisher/authorization 文档并阅读所有要实施的内容
@幻灯片放映2。你能解决这个问题吗?我仍然遇到这个问题,因为 access_type 处于脱机状态
J
Jason Sultana

如果您在 postman / insomnia 中对此进行测试,并且只是想使其正常工作,请提示:服务器身份验证代码(代码参数)只有一次有效。这意味着如果您在请求中填充任何其他参数并返回 400,您将需要使用新的服务器身份验证代码,否则您将只获得另一个 400。


在找到这个答案之前无数次尝试相同的代码。你拯救了我的一天。奇怪的是,这个答案没有显示在顶部。我需要向下滚动才能找到它。
T
Tony Vu

我遇到了同样的问题。对我来说,我通过使用电子邮件地址(以 ...@developer.gserviceaccount.com 结尾的字符串)而不是 client_id 参数值的客户端 ID 来解决此问题。谷歌设置的命名在这里令人困惑。


这与一年多前@aroth 给出的答案相同
K
Kevin Murphy

我们尝试了很多东西,最终问题是客户在他们的 Google 帐户设置中关闭了“不太安全的应用程序访问”。

要打开它:

转到 https://myaccount.google.com/ 并管理帐户 转到安全选项卡 打开不太安全的应用访问

https://i.stack.imgur.com/aHLlM.png

我希望这可以节省一些时间!


直接链接:myaccount.google.com/lesssecureapps 如果启用了双因素授权,它将不起作用。
B
Brad Lee

我的问题是我使用了这个 URL:

https://accounts.google.com/o/oauth2/token

当我应该使用这个 URL 时:

https://www.googleapis.com/oauth2/v4/token

这是在测试一个希望离线访问 Storage engine 的服务帐户。


C
Community

这是一个愚蠢的答案,但对我来说问题是我没有意识到我已经为我的谷歌用户颁发了一个有效的 oAuth 令牌,但我未能存储。这种情况下的解决方案是去 api 控制台并重置客户端密码。

关于这种效果还有许多其他答案,例如Reset Client Secret OAuth2 - Do clients need to re-grant access?


J
Justin Fiedler

使用 Android clientId (no client_secret) 我收到以下错误响应:

{
 "error": "invalid_grant",
 "error_description": "Missing code verifier."
}

我找不到字段“code_verifier”的任何文档,但我发现如果您在授权和令牌请求中将其设置为相等的值,它将消除此错误。我不确定预期的价值应该是什么,或者它是否应该是安全的。它有一些最小长度(16 个字符),但我发现设置为 null 也可以。

我在具有 setCodeVerifier() 功能的 Android 客户端中使用 AppAuth 进行授权请求。

AuthorizationRequest authRequest = new AuthorizationRequest.Builder(
                                    serviceConfiguration,
                                    provider.getClientId(),
                                    ResponseTypeValues.CODE,
                                    provider.getRedirectUri()
                            )
                            .setScope(provider.getScope())
                            .setCodeVerifier(null)
                            .build();

这是节点中的示例令牌请求:

request.post(
  'https://www.googleapis.com/oauth2/v4/token',
  { form: {
    'code': '4/xxxxxxxxxxxxxxxxxxxx',
    'code_verifier': null,
    'client_id': 'xxxxxxxxxxxxxxxxxxxxxx.apps.googleusercontent.com',
    'client_secret': null,
    'redirect_uri': 'com.domain.app:/oauth2redirect',
    'grant_type': 'authorization_code'
  } },
  function (error, response, body) {
    if (!error && response.statusCode == 200) {
      console.log('Success!');
    } else {
      console.log(response.statusCode + ' ' + error);
    }

    console.log(body);
  }
);

我进行了测试,这适用于 https://www.googleapis.com/oauth2/v4/tokenhttps://accounts.google.com/o/oauth2/token

如果您改用 GoogleAuthorizationCodeTokenRequest

final GoogleAuthorizationCodeTokenRequest req = new GoogleAuthorizationCodeTokenRequest(
                    TRANSPORT,
                    JSON_FACTORY,
                    getClientId(),
                    getClientSecret(),
                    code,
                    redirectUrl
);
req.set("code_verifier", null);          
GoogleTokenResponse response = req.execute();

C
Community

invalid_grant 错误有两个主要原因,您必须在 POST 请求刷新令牌和访问令牌之前注意这些原因。

请求标头必须包含“content-type: application/x-www-form-urlencoded” 您的请求负载应该是 url 编码的表单数据,不要作为 json 对象发送。

RFC 6749 OAuth 2.0invalid_grant 定义为:提供的授权授权(例如,授权代码、资源所有者凭据)或刷新令牌无效、已过期、已撤销,与授权请求,或已发给其他客户。

我找到了另一篇好文章,here你会发现这个错误的许多其他原因。

https://blog.timekit.io/google-oauth-invalid-grant-nightmare-and-how-to-fix-it-9f4efaf1da35


您的意思是发布两个几乎相同的答案吗?您可能希望删除这一行,因为另一行有额外的一行。
A
AlbertS

通过在项目的 Google 控制台中删除所有授权重定向 URI 来解决。当您使用“postmessage”作为重定向 URI 时,我使用服务器端流程


M
Mihai Crăiță

我有相同的错误消息“invalid_grant”,这是因为服务器上未正确接收从客户端 javascript 发送的 authResult['code']。

尝试从服务器输出它,看看它是否正确,而不是一个空字符串。


F
FelixSFD

尝试将您的网址更改为

https://www.googleapis.com/oauth2/v4/token

C
Community

您可能必须删除陈旧/无效的 OAuth 响应。

学分:node.js google oauth2 sample stopped working invalid_grant

注意:如果初始授权中使用的密码已更改,OAuth 响应也将失效。

如果在 bash 环境中,您可以使用以下内容删除过时的响应:

rm /Users/<username>/.credentials/<authorization.json>


D
Dharman

您在用户同意后在 URL 中获得的代码的有效期很短。请再次获取代码并尝试在几秒钟内获取访问令牌(您必须快点),它应该可以工作。我无法找到代码的有效期,但它确实很短。


没有看到任何文档!你让我的夜晚更短<3
A
AlexeyVMP

在我的例子中,它是一个与原始请求不同的回调 URL。因此,验证请求和代码交换的回调 URL 应该相同。


O
Oleksii Kyslytsyn

如果您正在使用 scribe 库,例如设置离线模式,如 bonkydog 建议的那样。这是代码:

OAuthService service = new ServiceBuilder().provider(Google2Api.class).apiKey(clientId).apiSecret(apiSecret)
                .callback(callbackUrl).scope(SCOPE).offline(true)
                .build();

https://github.com/codolutions/scribe-java/


u
user5699596

在这个网站console.developers.google.com

此控制台板选择您的项目输入誓言网址。 oauth 成功时,oauth 回调 url 将重定向


l
lwdthe1

在考虑并尝试了此处的所有其他方法之后,以下是我使用 googleapis 模块和 request 模块来解决 nodejs 中的问题的方法,我用它来获取令牌而不是提供的 getToken() 方法:

const request = require('request');

//SETUP GOOGLE AUTH
var google = require('googleapis');
const oAuthConfigs = rootRequire('config/oAuthConfig')
const googleOAuthConfigs = oAuthConfigs.google

//for google OAuth: https://github.com/google/google-api-nodejs-client
var OAuth2 = google.auth.OAuth2;
var googleOAuth2Client = new OAuth2(
    process.env.GOOGLE_OAUTH_CLIENT_ID || googleOAuthConfigs.clientId, 
    process.env.GOOGLE_OAUTH_CLIENT_SECRET || googleOAuthConfigs.clientSecret, 
    process.env.GOOGLE_OAUTH_CLIENT_REDIRECT_URL || googleOAuthConfigs.callbackUrl);

/* generate a url that asks permissions for Google+ and Google Calendar scopes
https://developers.google.com/identity/protocols/googlescopes#monitoringv3*/
var googleOAuth2ClientScopes = [
    'https://www.googleapis.com/auth/plus.me',
    'https://www.googleapis.com/auth/userinfo.email'
];

var googleOAuth2ClientRedirectURL = process.env.GOOGLE_OAUTH_CLIENT_REDIRECT_URL || googleOAuthConfigs.callbackUrl; 

var googleOAuth2ClientAuthUrl = googleOAuth2Client.generateAuthUrl({
  access_type: 'offline', // 'online' (default) or 'offline' (gets refresh_token)
  scope: googleOAuth2ClientScopes // If you only need one scope you can pass it as string
});

//AFTER SETUP, THE FOLLOWING IS FOR OBTAINING TOKENS FROM THE AUTHCODE


        const ci = process.env.GOOGLE_OAUTH_CLIENT_ID || googleOAuthConfigs.clientId
        const cs = process.env.GOOGLE_OAUTH_CLIENT_SECRET || googleOAuthConfigs.clientSecret
        const ru = process.env.GOOGLE_OAUTH_CLIENT_REDIRECT_URL || googleOAuthConfigs.callbackUrl
        var oauth2Client = new OAuth2(ci, cs, ru);

        var hostUrl = "https://www.googleapis.com";
        hostUrl += '/oauth2/v4/token?code=' + authCode + '&client_id=' + ci + '&client_secret=' + cs + '&redirect_uri=' + ru + '&grant_type=authorization_code',
        request.post({url: hostUrl}, function optionalCallback(err, httpResponse, data) {
            // Now tokens contains an access_token and an optional refresh_token. Save them.
            if(!err) {
                //SUCCESS! We got the tokens
                const tokens = JSON.parse(data)
                oauth2Client.setCredentials(tokens);

                //AUTHENTICATED PROCEED AS DESIRED.
                googlePlus.people.get({ userId: 'me', auth: oauth2Client }, function(err, response) {
                // handle err and response
                    if(!err) {
                        res.status(200).json(response);
                    } else {
                        console.error("/google/exchange 1", err.message);
                        handleError(res, err.message, "Failed to retrieve google person");
                    }
                });
            } else {
                console.log("/google/exchange 2", err.message);
                handleError(res, err.message, "Failed to get access tokens", err.code);
            }
        });

我只是使用 request 通过 HTTP 发出 api 请求,如下所述:https://developers.google.com/identity/protocols/OAuth2WebServer#offline

POST /oauth2/v4/token HTTP/1.1
Host: www.googleapis.com
Content-Type: application/x-www-form-urlencoded

code=4/P7q7W91a-oMsCeLvIaQm6bTrgtp7&
client_id=8819981768.apps.googleusercontent.com&
client_secret={client_secret}&
redirect_uri=https://oauth2.example.com/code&
grant_type=authorization_code

K
Kanishk Gupta

对于未来的人......我阅读了很多文章和博客,但很幸运能找到下面的解决方案......

GoogleTokenResponse tokenResponse =
      new GoogleAuthorizationCodeTokenRequest(
          new NetHttpTransport(),
          JacksonFactory.getDefaultInstance(),
          "https://www.googleapis.com/oauth2/v4/token",
          clientId,
          clientSecret,
          authCode,
          "") //Redirect Url
     .setScopes(scopes)
     .setGrantType("authorization_code")
     .execute();

This 博客描述了出现“invalid_grant”错误的不同情况。

享受!!!


W
Waqleh

对我来说,我必须确保 redirect_uri 与开发者控制台 Authorised redirect URIs 中的完全匹配,这为我修复了它,我能够调试并知道从 {3 切换后到底是什么问题} 到 https://www.googleapis.com/oauth2/v4/token

我得到了一个正确的错误:

{"error": "redirect_uri_mismatch",  "error_description": "Bad Request"}

D
DIMMACK

在 Google 控制台上启用新的服务 API 并尝试使用之前制作的凭据后,我遇到了这个问题。

要解决此问题,我必须返回凭证页面,单击凭证名称,然后再次单击“保存”。在那之后,我可以很好地进行身份验证。


K
Kate Kasinskaya

就我而言,问题出在我的代码中。我错误地尝试使用相同的令牌启动客户端 2 次。如果上述答案都没有帮助确保您不会生成 2 个客户端实例。

修复前我的代码:

def gc_service
      oauth_client = Signet::OAuth2::Client.new(client_options)
      oauth_client.code = params[:code]
      response = oauth_client.fetch_access_token!
      session[:authorization] = response
      oauth_client.update!(session[:authorization])

      gc_service = Google::Apis::CalendarV3::CalendarService.new
      gc_service.authorization = oauth_client

      gc_service
    end
primary_calendar_id = gc_service.list_calendar_lists.items.select(&:primary).first.id

gc_service.insert_acl(primary_calendar_id, acl_rule_object, send_notifications: false)

一旦我将其更改为(仅使用一个实例):

@gc_service = gc_service
primary_calendar_id = @gc_service.list_calendar_lists.items.select(&:primary).first.id

@gc_service.insert_acl(primary_calendar_id, acl_rule_object, send_notifications: false)

它解决了我的授权类型问题。


S
Subrata Fouzdar

对我来说,问题是我的项目中有多个客户端,我很确定这完全没问题,但是我删除了该项目的所有客户端并创建了一个新客户端,所有这些都开始为我工作(从 WP_SMTP 插件帮助中获得了这个想法支持论坛)我无法找到该链接以供参考


n
nathanfranke

如果您正在清理用户输入(例如 php 中的 $_GET["code"]),请确保您不会意外替换代码中的某些内容。

我正在使用的正则表达式现在是 /[^A-Za-z0-9\/-]/


K
Konstantin XFlash Stratigenas

看看这个https://dev.to/risafj/beginner-s-guide-to-oauth-understanding-access-tokens-and-authorization-codes-2988

首先你需要一个access_token:

$code = $_GET['code'];

$clientid = "xxxxxxx.apps.googleusercontent.com";
$clientsecret = "xxxxxxxxxxxxxxxxxxxxx";

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "https://www.googleapis.com/oauth2/v4/token");
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, "client_id=".urlencode($clientid)."&client_secret=".urlencode($clientsecret)."&code=".urlencode($code)."&grant_type=authorization_code&redirect_uri=". urlencode("https://yourdomain.com"));
curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: application/x-www-form-urlencoded'));

curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$server_output = curl_exec($ch);
curl_close ($ch);

$server_output = json_decode($server_output);
$access_token = $server_output->access_token;
$refresh_token = $server_output->refresh_token;
$expires_in = $server_output->expires_in;

保护数据库中的访问令牌和刷新令牌以及 expire_in。访问令牌在 $expires_in 秒后过期。比您需要使用以下请求获取新的访问令牌(并将其保存在数据库中):

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "https://www.googleapis.com/oauth2/v4/token");
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, "client_id=".urlencode($clientid)."&client_secret=".urlencode($clientsecret)."&refresh_token=".urlencode($refresh_token)."&grant_type=refresh_token");
curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: application/x-www-form-urlencoded'));

curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$server_output = curl_exec($ch);
curl_close ($ch);

$server_output = json_decode($server_output);
$access_token = $server_output->access_token;
$expires_in = $server_output->expires_in;

请记住将 redirect_uri 域添加到您的 Google 控制台中的域:https://console.cloud.google.com/apis/credentials 在“OAuth 2.0-Client-IDs”选项卡中。在那里您还可以找到您的客户 ID 和客户秘密。


s
sparkyspider

在您第一次将用户重定向到 google 身份验证页面(并取回代码)与您获取返回的代码并将其发布到令牌 url 之间存在未记录的超时。使用谷歌提供的实际 client_id 而不是“未记录的电子邮件地址”,它对我来说很好。我只需要重新开始这个过程。


t
thisismydesign

对我来说,这是由使用相同代码的后续 getToken 调用引起的。

也就是说,在 NestJS 中,我的回调端点用 @UseGuards(AuthGuard('google')) 装饰,我尝试在回调中调用 getToken


F
Fetchinator7

在我的情况下,我只是没有正确阅读 documentation,因为我每次都尝试执行 const { tokens } = await oauth2Client.getToken(accessToken); 以获得授权的客户端实例,但是对于后续请求,您只需要包含您在第一个之后存储的 refresh_token用户授权。

oauth2Client.setCredentials({
  refresh_token: `STORED_REFRESH_TOKEN`
});

K
Khribi Wessim

对我来说,我必须在测试 Google 连接链接 Api 之前删除网络 cookie。