Reputation: 6726
I keep getting an invalid_grant
error on trying to get an OAuth token from Google to connect to their contacts API. All the information is correct and I have triple-checked this so I'm kind of stumped.
Does anyone know what may be causing this issue? I have tried setting up a different client ID for it but I get the same result. I have tried connecting many different ways including trying the force authentication, but still the same result.
Upvotes: 257
Views: 519106
Reputation: 517
I tried almost everything in this thread but nothing worked for me.
What solved it for me was to go to the Google Cloud Console
API & Services -> OAuth consent screen -> edit app -> save & continue -> scopes ->
(add the scopes for what you're trying to access. for me this was the auth/gmail.readonly
scope) -> save
.
they say you need to verify your app because the scope is for restricted content but it still worked for me, i guess because the email i was trying to access was the same email authorized in the google cloud console
Upvotes: 1
Reputation: 31
I ran into this problem recently and eventually learned that the issue was on my side, not Google's. My locally-running UI had React.StrictMode
enabled, which caused the relevant OAuth callback circuitry to render twice, failing the second time the same code was used.
If your network tab shows two requests, the first of which succeeds, perhaps this is your issue as well.
Upvotes: 1
Reputation: 195
If you are using GoogleCredentials.getApplicationDefault()
and get the invalid_grant
error then you can try to re-login with gcloud auth application-default login
Upvotes: 0
Reputation: 988
In my case I was trying to use gcloud APIs through a Go program running locally. I recently changed my user's password so I first tried gcloud auth login
but it kept outputing the same invalid_grant
error.
The catch is that you are not interacting directly with the api, your code is, so you have to use gcloud auth application-default login
instead. That fixed it for me.
Upvotes: 8
Reputation: 537
Generating refresh and access token:
Replace the essential values (code, client_id, client_secret) inside payload.
let payload = {
grant_type: 'authorization_code',
code:'********FDLJli-DFJNLDLfKJ',
client_id: '******.googleusercontent.com',
client_secret: 'GOCS*******m5Qzg',
redirect_uri: 'http://localhost:3000',
};
axios
.post(`https://oauth2.googleapis.com/token`, payload, {
headers: {
'Content-Type': 'application/json;',
},
})
.then((res: any) => {
return res.data;
})
.then((response: any) => {
console.log('refresh token: ', response);
})
.catch((err) => console.log('err: ', err));
You will get a response like this:
{
access_token: "********KAjJZmv4xLvbAIHey",
expires_in: 3599,
id_token: "***************VeM7cfmgbvVIg",
refresh_token: "***************VeM7cfmgbvVIg",
scope: "https://www.googleapis.com/auth/gmail.readonly openid
.....
.....
https://mail.google.com/",
token_type: "Bearer",
}
Save your refresh and access token. Now you can use refresh token to generate new access token! Here is an article on how you can integrate this on your react application. You can check this out for more details.
Note: Do not request again and again for refresh token. They provide it only once. I faced some issues while requesting refresh token for more than 1 time.
Upvotes: 0
Reputation: 2683
Although this is an old question, it seems like many still encounter it - we spent days on end tracking this down ourselves.
In the OAuth2 spec, "invalid_grant" is sort of a catch-all for all errors related to invalid/expired/revoked tokens (auth grant or refresh token).
For us, the problem was two-fold:
User has actively revoked access to our app
Makes sense, but get this: 12 hours after revocation, Google stops sending the error message in their response:
“error_description” : “Token has been revoked.”
It's rather misleading because you'll assume that the error message is there at all times which is not the case. You can check whether your app still has access at the apps permission page.
User has reset/recovered their Google password
In December 2015, Google changed their default behaviour so that password resets for non-Google Apps users would automatically revoke all the user's apps refresh tokens. On revocation, the error message follows the same rule as the case before, so you'll only get the "error_description" in the first 12 hours. There doesn't seem to be any way of knowing whether the user manually revoked access (intentful) or it happened because of a password reset (side-effect).
Apart from those, there's a myriad of other potential causes that could trigger the error:
I've written a short article summarizing each item with some debugging guidance to help find the culprit.
Upvotes: 203
Reputation: 106102
None of the answer worked for me. After calling
https://accounts.google.com/o/oauth2/v2/auth?redirect_uri=<CALLBACK_URL_YOU_SET_ON_GOOGLE>&prompt=consent&response_type=code&client_id=<YOUR CLIENT ID>&scope=openid%20email%20profile&access_type=offline
I was getting response url in return which had code
value
4%2F0ARtbsJoGfJYFT6Cy1VpPiONgYV1vIzVhGtTK9MvyQLZfuGv_sAPDRDOzqGkRu07qOIxBfg
Instead of decoding the the url received I copied this value and passed to my app API and got the reported error invalid_grant
.
%2F
in the above code should be replaced by /
. It should be
4/0ARtbsJoGfJYFT6Cy1VpPiONgYV1vIzVhGtTK9MvyQLZfuGv_sAPDRDOzqGkRu07qOIxBfg
Note that this code
works only once.
Lesson: Always decode the received url!
Upvotes: 3
Reputation: 1149
Creation of the new Credentials key under:
Was the honey for me.
from:
to:
Upvotes: 2
Reputation: 355
This can happen if your redirect_url is not the same as the one you have when creating the token on Google Gloud. So make sure it's correct
Upvotes: 2
Reputation: 722
For me, I got to remove web cookies before testing Google connect link Api.
Upvotes: 0
Reputation: 166
I my case I just didn't read the documentation properly because I was trying to do const { tokens } = await oauth2Client.getToken(accessToken);
every time to get an authorized client instance but for the subsequent requests you only need to include the refresh_token
you store after the first user auth.
oauth2Client.setCredentials({
refresh_token: `STORED_REFRESH_TOKEN`
});
Upvotes: 4
Reputation: 2426
if you are using scribe library, for example to set up the offline mode, like bonkydog suggested. here is the code:
OAuthService service = new ServiceBuilder().provider(Google2Api.class).apiKey(clientId).apiSecret(apiSecret)
.callback(callbackUrl).scope(SCOPE).offline(true)
.build();
https://github.com/codolutions/scribe-java/
Upvotes: 2
Reputation: 2426
In my case it was a callback URL that was different from the original request. So, callback URL should be the same for auth request and code exchange.
Upvotes: 2
Reputation: 25152
For me, this was caused by subsequent getToken
calls with the same code.
Namely, in NestJS my callback endpoint was decorated with @UseGuards(AuthGuard('google'))
and I tried to call getToken
in the callback.
Upvotes: 1
Reputation: 456
We tried so many things, and in the end the issue was that the client had turned "Less Secure App Access" off in their Google Account settings.
To turn this on:
I hope this saves someone some time!
Upvotes: 11
Reputation: 116
The code you obtain in the URL after user consent has a very short expiry. Please obtain the code again and attempt to get access token within seconds (you have to hurry) and it should work. I can't find out the expiry period of code but it's literally very short.
Upvotes: 4
Reputation: 736
Solved by removing all Authorized redirect URIs in Google console for the project. I use server side flow when you use 'postmessage' as redirect URI
Upvotes: 5
Reputation: 1179
If you're testing this out in postman / insomnia and are just trying to get it working, hint: the server auth code (code parameter) is only good once. Meaning if you stuff up any of the other parameters in the request and get back a 400, you'll need to use a new server auth code or you'll just get another 400.
Upvotes: 60
Reputation: 13529
There is a undocumented timeout between when you first redirect the user to the google authentication page (and get back a code), and when you take the returned code and post it to the token url. It works fine for me with the actual google supplied client_id as opposed to an "undocumented email address". I just needed to start the process again.
Upvotes: 1
Reputation: 607
Look at this https://dev.to/risafj/beginner-s-guide-to-oauth-understanding-access-tokens-and-authorization-codes-2988
First you need an 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;
Safe the Access Token and the Refresh Token and the expire_in, in a Database. The Access Token expires after $expires_in seconds. Than you need to grab a new Access Token (and safe it in the Database) with the following Request:
$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;
Bear in Mind to add the redirect_uri Domain to your Domains in your Google Console: https://console.cloud.google.com/apis/credentials in the Tab "OAuth 2.0-Client-IDs". There you find also your Client-ID and Client-Secret.
Upvotes: 1
Reputation: 991
If you are sanitizing user input (For example, $_GET["code"]
in php) Make sure you don't accidentally replace something in the code.
The regex I am using is now /[^A-Za-z0-9\/-]/
Upvotes: 1
Reputation: 733
For me the issues was I had multiple clients in my project and I am pretty sure this is perfectly alright, but I deleted all the client for that project and created a new one and all started working for me ( Got this idea fro WP_SMTP plugin help support forum) I am not able to find out that link for reference
Upvotes: 1
Reputation: 933
In my case, the issue was in my code. Mistakenly I've tried to initiate client 2 times with the same tokens. If none of the answers above helped make sure you do not generate 2 instances of the client.
My code before the fix:
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)
as soon as I change it to (use only one instance):
@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)
it fixed my issues with grant type.
Upvotes: 1
Reputation: 197
I had this problem after enabling a new service API on the Google console and trying to use the previously made credentials.
To fix the problem, I had to go back to the credential page, clicking on the credential name, and clicking "Save" again. After that, I could authenticate just fine.
Upvotes: 1
Reputation: 10171
for me I had to make sure that the redirect_uri
is an exact match to the one in the developer console Authorised redirect URIs
, that fixed it for me, I was able to debug and know what exactly was the issue after switching from
https://accounts.google.com/o/oauth2/token
to https://www.googleapis.com/oauth2/v4/token
I got a proper error:
{"error": "redirect_uri_mismatch", "error_description": "Bad Request"}
Upvotes: 1
Reputation: 379
For future folks... I read many articles and blogs but had luck with solution below...
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 blog depicts different cases in which "invalid_grant" error comes.
Enjoy!!!
Upvotes: 1
Reputation: 159
There are two major reasons for invalid_grant error which you have to take care prior to the POST request for Refresh Token and Access Token.
RFC 6749 OAuth 2.0 defined invalid_grant as: The provided authorization grant (e.g., authorization code, resource owner credentials) or refresh token is invalid, expired, revoked, does not match the redirection URI used in the authorization request, or was issued to another client.
I found another good article, here you will find many other reasons for this error.
https://blog.timekit.io/google-oauth-invalid-grant-nightmare-and-how-to-fix-it-9f4efaf1da35
Upvotes: 4
Reputation: 3451
You might have to remove a stale/invalid OAuth response.
Credit: node.js google oauth2 sample stopped working invalid_grant
Note: An OAuth response will also become invalid if the password used in the initial authorization has been changed.
If in a bash environment, you can use the following to remove the stale response:
rm /Users/<username>/.credentials/<authorization.json>
Upvotes: 3
Reputation: 1142
This is a silly answer, but the problem for me was that I failed to realize I already had been issued an active oAuth token for my google user which I failed to store. The solution in this case is to go to the api console and reset the client secret.
There are numerous other answers on SO to this effect for example Reset Client Secret OAuth2 - Do clients need to re-grant access?
Upvotes: 7
Reputation: 6576
Using a Android clientId (no client_secret) I was getting the following error response:
{
"error": "invalid_grant",
"error_description": "Missing code verifier."
}
I cannot find any documentation for the field 'code_verifier' but I discovered if you set it to equal values in both the authorization and token requests it will remove this error. I'm not sure what the intended value should be or if it should be secure. It has some minimum length (16? characters) but I found setting to null
also works.
I am using AppAuth for the authorization request in my Android client which has a setCodeVerifier()
function.
AuthorizationRequest authRequest = new AuthorizationRequest.Builder(
serviceConfiguration,
provider.getClientId(),
ResponseTypeValues.CODE,
provider.getRedirectUri()
)
.setScope(provider.getScope())
.setCodeVerifier(null)
.build();
Here is an example token request in node:
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);
}
);
I tested and this works with both https://www.googleapis.com/oauth2/v4/token
and https://accounts.google.com/o/oauth2/token
.
If you are using GoogleAuthorizationCodeTokenRequest
instead:
final GoogleAuthorizationCodeTokenRequest req = new GoogleAuthorizationCodeTokenRequest(
TRANSPORT,
JSON_FACTORY,
getClientId(),
getClientSecret(),
code,
redirectUrl
);
req.set("code_verifier", null);
GoogleTokenResponse response = req.execute();
Upvotes: 4