ChatGPT解决这个技术问题 Extra ChatGPT

invalid_grant trying to get oAuth token from google

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 tripple checked this so 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.

for me the problem was on google credentials page...i created another one...and solved the problem....
I had this issue recently and it also happens when you try to request the first access token with refresh token again, then the invalid_grant also shows up.. I solved it by resetting the secret and then cathing the first tokens on the first request.. if I failed and tried again I would get the invalid_grant again.. first time you need to catch the tokens save and then only refresh with refresh token otherwuse you are "invalid"
@Sagan care to explain your process and sequence again?
@MikeyB a bit late but sure, if you are getting the invalid grant error due to the same reason like me then you solve it by resetting the secret key in the google console and go through the token generation flow again with the new secret key, but don't forget to save the refresh token which is only generated once, then you can generate access tokens using the refresh token.
@Sagan but this you will be doing again in the next 7 days when it is revoked again. Is there a permanent or rather a long lasting way of doing this?

l
laander

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:

Server clock/time is out of sync Not authorized for offline access Throttled by Google Using expired refresh tokens User has been inactive for 6 months Use service worker email instead of client ID Too many access tokens in short time Client SDK might be outdated Incorrect/incomplete refresh token

I've written a short article summarizing each item with some debugging guidance to help find the culprit. Hope it helps.


Another scenario is if you try to get tokens multiple times form the same auth code.
That was exactly my problem, simply revoking the app by accident. Then had to rerun the refreshToken.php through terminal to generate another authorization code and then replace the refreshToken everywhere for this clientID.
In my case, I changed my password a month ago. I found out that I need to give permission to the app again and get the new refresh token
a
aroth

I ran into this same problem despite specifying the "offline" access_type in my request as per bonkydog's answer. Long story short I found that the solution described here worked for me:

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

In essence, when you add an OAuth2 Client in your Google API's console Google will give you a "Client ID" and an "Email address" (assuming you select "webapp" as your client type). And despite Google's misleading naming conventions, they expect you to send the "Email address" as the value of the client_id parameter when you access their OAuth2 API's.

This applies when calling both of these URL's:

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

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

Note that the call to the first URL will succeed if you call it with your "Client ID" instead of your "Email address". However using the code returned from that request will not work when attempting to get a bearer token from the second URL. Instead you will get an 'Error 400' and an "invalid_grant" message.


Absolutely ridiculous: Especially the part where it works with the client_id if you get an initial refresh token. Googles API and their documentation is a mess.
I was banging my head against this issue for so many hours. I never expected that 'client_id' was not what was expected for the 'client_id' field. Except the occasional time when you do get a refresh_token and it does work. Pretty sure the words I have for google at the moment can't be said on SO.
Hi there.. Im unable to find that "email" address you guys are talking about. this is what I have in my console --> pbs.twimg.com/media/CVVcEBWUwAAIiCy.png:large
Where is that email Address? I'm having the same issue
Replacing the client_id with the user's email gives a invalid_client error. This answer seems outdated.
b
bonkydog

I ran into this problem when I didn't explicitly request "offline" access when sending the user to the OAuth "Do you want to give this app permission to touch your stuff?" page.

Make sure you specify access_type=offline in your request.

Details here: https://developers.google.com/accounts/docs/OAuth2WebServer#offline

(Also: I think Google added this restriction in late 2011. If you have old tokens from before then, you'll need to send your users to the permission page to authorize offline use.)


@Adders I agree. I set access_type to offline, this error still happens.
Go through this developers.google.com/android-publisher/authorization documentation and read everything to implement
@slideshowp2. were you able to resolve this? i am still getting this issue with acccess_type being offline
J
Jason Sultana

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.


Trying the same code countless time before found this answer. You saved my day. It's strange that this answer not shown on top. I need to scroll down to find this.
T
Tony Vu

I encountered the same problem. For me, I fixed this by using Email Address (the string that ends with ...@developer.gserviceaccount.com) instead of Client ID for client_id parameter value. The naming set by Google is confusing here.


This is the same answer given by @aroth more than a year earlier
K
Kevin Murphy

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:

Go to https://myaccount.google.com/ and manage account Go to the Security tab Turn Less secure app access on

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

I hope this saves someone some time!


Direct link: myaccount.google.com/lesssecureapps It's not working if two-factor authorization is enabled.
B
Brad Lee

My issue was that I used this URL:

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

When I should have used this URL:

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

This was testing a service account which wanted offline access to the Storage engine.


C
Community

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?


J
Justin Fiedler

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

C
Community

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.

Request header must contain "content-type: application/x-www-form-urlencoded" Your request payload should be url encoded Form Data, don't send as json object.

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


Did you mean to post two nearly identical answers? You might want to delete this one because the other has an additional line.
A
AlbertS

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


M
Mihai Crăiță

I had the same error message 'invalid_grant' and it was because the authResult['code'] send from client side javascript was not received correctly on the server.

Try to output it back from the server to see if it is correct and not an empty string.


F
FelixSFD

Try change your url for requst to

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

C
Community

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>


D
Dharman

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.


Didn't see it any of the docs! You made my night shorter <3
A
AlexeyVMP

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.


O
Oleksii Kyslytsyn

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/


u
user5699596

in this site console.developers.google.com

this console board select your project input the oath url. the oauth callback url will redirect when the oauth success


l
lwdthe1

After considering and trying all of the other ways here, here's how I solved the issue in nodejs with the googleapis module in conjunction with the request module, which I used to fetch the tokens instead of the provided getToken() method:

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

I simply use request to make the api request via HTTP as described here: 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

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!!!


W
Waqleh

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"}

D
DIMMACK

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.


K
Kate Kasinskaya

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.


S
Subrata Fouzdar

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


n
nathanfranke

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\/-]/


K
Konstantin XFlash Stratigenas

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.


s
sparkyspider

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.


t
thisismydesign

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.


F
Fetchinator7

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`
});

K
Khribi Wessim

For me, I got to remove web cookies before testing Google connect link Api.