invalid_grant试图从Google获取oAuth令牌


120

我一直invalid_grant在尝试从Google获取oAuth令牌以连接到其联系人api时遇到错误。所有的信息都是正确的,我已经三联检查过这种情况。

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


对我来说,问题出在Google凭证页面上...我创建了另一个...并解决了问题....
costamatrix

Answers:


59

当我向用户发送OAuth时没有明确请求“脱机”访问时,我遇到了这个问题。“您是否要授予此应用触摸内容的权限?” 页。

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

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

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


9
@Adders,我同意。我设置access_typeoffline,此错误仍然会发生。
slideshowp2

这不应该是公认的答案。
Kishan Solanki

仔细

70

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

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

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

这在调用以下两个URL时适用:

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


66
绝对荒谬:尤其是当您获得初始刷新令牌时,它与client_id一起使用的部分。Google的API及其文档非常混乱。
Traubenfuchs 2015年

7
我花了好几个小时来研究这个问题。我从没想到'client_id'不是'client_id'字段所期望的。除了偶尔的时间,当您确实获得了refresh_token并可以正常工作时。可以肯定的是,我目前在Google上所说的话不能在SO上说出来。
贾斯汀

6
您好,我无法找到您正在谈论的“电子邮件”地址。这就是我控制台中的内容-> pbs.twimg.com/media/CVVcEBWUwAAIiCy.png:large
omarojo

4
该电子邮件地址在哪里?我遇到了同样的问题
Isma Haro 2015年

4
永远不要相信Google文档。最糟糕的文档和API来自世界上最有价值的公司Google。我不得不花费无数小时来使用Google API。有问题接连出现,由于不同的依赖性问题和所有问题,针对不同API的自己的.Net库没有一起编译。现在,该代码对于大多数用户来说效果很好,但是对于某些用户,由于某些特殊原因,我仍然会得到invalid_grant,invalid_credentials等。
艾伦·金

56

尽管这是一个古老的问题,但似乎仍有很多人遇到了我们-我们花了数天的时间自己追踪这个问题。

在OAuth2规范中,“ invalid_grant”是针对与无效/过期/吊销的令牌(身份验证授权或刷新令牌)相关的所有错误的综合解决方案。

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

  1. 用户主动撤消了对我们应用的访问这样
    做是有道理的,但是请注意:撤消12小时后,Google停止在其响应中发送错误消息: “error_description” : “Token has been revoked.”
    这很容易引起误解,因为您会假设错误消息一直存在案子。您可以在“ 应用程序权限”页面上检查您的应用程序是否仍然具有访问权限

  2. 用户已重置/恢复了自己的Google密码
    2015年12月,Google更改了默认行为,以便为非Google Apps用户重置密码会自动撤消所有用户的应用刷新令牌。吊销后,错误消息将遵循与以前相同的规则,因此您只会在前12小时内收到“ error_description”。似乎没有任何方法可以知道用户是手动撤消了访问(故意的)还是由于密码重置而发生了(副作用)。

除此之外,还有许多其他可能导致错误的潜在原因:

  1. 服务器时钟/时间不同步
  2. 无权进行离线访问
  3. 被Google扼杀
  4. 使用过期的刷新令牌
  5. 用户已停用6个月
  6. 使用服务人员电子邮件代替客户端ID
  7. 短时间内访问令牌过多
  8. 客户端SDK可能已过时
  9. 刷新令牌不正确/不完整

我写了一篇简短的文章,对每个项目进行了概述,并提供了一些调试指导,以帮助找到罪魁祸首。希望能帮助到你。


1
另一种情况是,如果您尝试通过同一身份验证代码多次获取令牌。
–knownasilya

那正是我的问题,只是无意间吊销了该应用程序。然后必须通过终端重新运行refreshToken.php来生成另一个授权代码,然后在该clientID的所有位置替换refreshToken。
罗伯特·辛克莱

7

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


6
这是@aroth一年多前面给出了相同的答案
布莱恩灰

3

我的问题是我使用了以下URL:

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

当我应该使用此URL时:

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

这是在测试一个服务帐户,该帐户希望脱机访问存储引擎


2

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

尝试将其从服务器输出回去,以查看它是否正确而不是空字符串。



1

使用Android clientId(无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();


1

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

信用:node.js谷歌oauth2示例停止工作invalid_grant

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

如果在bash环境中,则可以使用以下命令来删除过时的响应:

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


1

无效_授予错误有两个主要原因,在对刷新令牌和访问令牌进行POST请求之前,您必须要小心。

  1. 请求标头必须包含“内容类型:application / x-www-form-urlencoded”
  2. 您的请求有效负载应为url编码的表单数据,请勿作为json对象发送。

RFC 6749 OAuth 2.0invalid_grant定义为: 提供的授权授权(例如,授权代码,资源所有者凭证)或刷新令牌无效,已过期,已吊销,与授权请求中使用的重定向URI不匹配,或已颁发给另一个客户端。

我找到了另一篇很好的文章,在这里您会发现此错误的许多其他原因。

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


您是要发布两个几乎相同的答案吗?您可能要删除一个,因为另一个有额外的一行。
高炉炉


0

在考虑并尝试了所有其他方式之后,下面是我如何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


0

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

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

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

请享用!!!


0

对我来说,我必须确保redirect_uri确实与开发人员控制台中的完全匹配Authorised redirect URIs,并为我修复了该问题,我能够调试并知道从切换https://accounts.google.com/o/oauth2/token到后的确切问题是什么。 https://www.googleapis.com/oauth2/v4/token

我有一个正确的错误:

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

0

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

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


0

就我而言,问题出在我的代码中。错误地,我尝试使用相同的令牌启动客户端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)

它解决了我与赠款类型有关的问题。


0

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


0

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

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


0

看看这个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控制台中的域中:“ OAuth 2.0-Client-IDs”标签中的https://console.cloud.google.com/apis/credentials。您还可以在其中找到您的客户ID和客户秘密。


0

从首次将用户重定向到google身份验证页面(并获取代码)到获取返回的代码并将其发布到令牌网址之间,这段时间没有记录。与实际的google提供的client_id(而不是“未公开的电子邮件地址”)相对,它对我来说效果很好。我只需要再次开始该过程。


0

如果您要在邮递员/失眠症患者中进行测试,并且只是想使其正常运行,则提示:服务器身份验证代码(代码参数)只可以使用一次。这意味着,如果您在请求中填充其他任何参数并取回400,则需要使用新的服务器身份验证代码,否则将仅获得400。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.