在尝试从 Google 获取 oAuth 令牌以连接到他们的联系人 API 时,我不断收到 invalid_grant
错误。所有的信息都是正确的,我已经检查了三次,这让我很困惑。
有谁知道可能导致此问题的原因?我尝试为其设置不同的客户端 ID,但得到相同的结果,我尝试连接许多不同的方式,包括尝试强制身份验证,但结果仍然相同。
虽然这是一个老问题,但似乎很多人仍然遇到它 - 我们花了几天时间自己追踪这个问题。
在 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 总结每个项目并提供一些调试指南,以帮助找到罪魁祸首。希望能帮助到你。
尽管根据 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
替换为用户的电子邮件会产生 invalid_client
错误。这个答案似乎已经过时了。
当我在将用户发送到 OAuth 时没有明确请求“离线”访问时遇到了这个问题“你想授予这个应用程序触摸你的东西的权限吗?”页。
确保在请求中指定 access_type=offline。
此处的详细信息:https://developers.google.com/accounts/docs/OAuth2WebServer#offline
(另外:我认为 Google 在 2011 年末添加了此限制。如果您有在那之前的旧令牌,您需要将您的用户发送到权限页面以授权离线使用。)
access_type
设置为 offline
,此错误仍然发生。
如果您在 postman / insomnia 中对此进行测试,并且只是想使其正常工作,请提示:服务器身份验证代码(代码参数)只有一次有效。这意味着如果您在请求中填充任何其他参数并返回 400,您将需要使用新的服务器身份验证代码,否则您将只获得另一个 400。
我遇到了同样的问题。对我来说,我通过使用电子邮件地址(以 ...@developer.gserviceaccount.com 结尾的字符串)而不是 client_id 参数值的客户端 ID 来解决此问题。谷歌设置的命名在这里令人困惑。
我们尝试了很多东西,最终问题是客户在他们的 Google 帐户设置中关闭了“不太安全的应用程序访问”。
要打开它:
转到 https://myaccount.google.com/ 并管理帐户 转到安全选项卡 打开不太安全的应用访问
https://i.stack.imgur.com/aHLlM.png
我希望这可以节省一些时间!
我的问题是我使用了这个 URL:
https://accounts.google.com/o/oauth2/token
当我应该使用这个 URL 时:
https://www.googleapis.com/oauth2/v4/token
这是在测试一个希望离线访问 Storage engine 的服务帐户。
这是一个愚蠢的答案,但对我来说问题是我没有意识到我已经为我的谷歌用户颁发了一个有效的 oAuth 令牌,但我未能存储。这种情况下的解决方案是去 api 控制台并重置客户端密码。
关于这种效果还有许多其他答案,例如Reset Client Secret OAuth2 - Do clients need to re-grant access?
使用 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/token
和 https://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();
invalid_grant 错误有两个主要原因,您必须在 POST 请求刷新令牌和访问令牌之前注意这些原因。
请求标头必须包含“content-type: application/x-www-form-urlencoded” 您的请求负载应该是 url 编码的表单数据,不要作为 json 对象发送。
RFC 6749 OAuth 2.0 将 invalid_grant 定义为:提供的授权授权(例如,授权代码、资源所有者凭据)或刷新令牌无效、已过期、已撤销,与授权请求,或已发给其他客户。
我找到了另一篇好文章,here你会发现这个错误的许多其他原因。
https://blog.timekit.io/google-oauth-invalid-grant-nightmare-and-how-to-fix-it-9f4efaf1da35
通过在项目的 Google 控制台中删除所有授权重定向 URI 来解决。当您使用“postmessage”作为重定向 URI 时,我使用服务器端流程
我有相同的错误消息“invalid_grant”,这是因为服务器上未正确接收从客户端 javascript 发送的 authResult['code']。
尝试从服务器输出它,看看它是否正确,而不是一个空字符串。
尝试将您的网址更改为
https://www.googleapis.com/oauth2/v4/token
您可能必须删除陈旧/无效的 OAuth 响应。
学分:node.js google oauth2 sample stopped working invalid_grant
注意:如果初始授权中使用的密码已更改,OAuth 响应也将失效。
如果在 bash 环境中,您可以使用以下内容删除过时的响应:
rm /Users/<username>/.credentials/<authorization.json>
您在用户同意后在 URL 中获得的代码的有效期很短。请再次获取代码并尝试在几秒钟内获取访问令牌(您必须快点),它应该可以工作。我无法找到代码的有效期,但它确实很短。
在我的例子中,它是一个与原始请求不同的回调 URL。因此,验证请求和代码交换的回调 URL 应该相同。
如果您正在使用 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/
在考虑并尝试了此处的所有其他方法之后,以下是我使用 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
对于未来的人......我阅读了很多文章和博客,但很幸运能找到下面的解决方案......
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”错误的不同情况。
享受!!!
对我来说,我必须确保 redirect_uri
与开发者控制台 Authorised redirect URIs
中的完全匹配,这为我修复了它,我能够调试并知道从 {3 切换后到底是什么问题} 到 https://www.googleapis.com/oauth2/v4/token
我得到了一个正确的错误:
{"error": "redirect_uri_mismatch", "error_description": "Bad Request"}
在 Google 控制台上启用新的服务 API 并尝试使用之前制作的凭据后,我遇到了这个问题。
要解决此问题,我必须返回凭证页面,单击凭证名称,然后再次单击“保存”。在那之后,我可以很好地进行身份验证。
就我而言,问题出在我的代码中。我错误地尝试使用相同的令牌启动客户端 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)
它解决了我的授权类型问题。
对我来说,问题是我的项目中有多个客户端,我很确定这完全没问题,但是我删除了该项目的所有客户端并创建了一个新客户端,所有这些都开始为我工作(从 WP_SMTP 插件帮助中获得了这个想法支持论坛)我无法找到该链接以供参考
如果您正在清理用户输入(例如 php 中的 $_GET["code"]
),请确保您不会意外替换代码中的某些内容。
我正在使用的正则表达式现在是 /[^A-Za-z0-9\/-]/
首先你需要一个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 和客户秘密。
在您第一次将用户重定向到 google 身份验证页面(并取回代码)与您获取返回的代码并将其发布到令牌 url 之间存在未记录的超时。使用谷歌提供的实际 client_id 而不是“未记录的电子邮件地址”,它对我来说很好。我只需要重新开始这个过程。
对我来说,这是由使用相同代码的后续 getToken
调用引起的。
也就是说,在 NestJS 中,我的回调端点用 @UseGuards(AuthGuard('google'))
装饰,我尝试在回调中调用 getToken
。
在我的情况下,我只是没有正确阅读 documentation,因为我每次都尝试执行 const { tokens } = await oauth2Client.getToken(accessToken);
以获得授权的客户端实例,但是对于后续请求,您只需要包含您在第一个之后存储的 refresh_token
用户授权。
oauth2Client.setCredentials({
refresh_token: `STORED_REFRESH_TOKEN`
});
对我来说,我必须在测试 Google 连接链接 Api 之前删除网络 cookie。
不定期副业成功案例分享