André Figueira
André Figueira

Reputation: 6726

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

Answers (30)

Phillip Musiime
Phillip Musiime

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

user3246857
user3246857

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

I V
I V

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

Mit94
Mit94

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

Sayad Ahmed Shaurov
Sayad Ahmed Shaurov

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

laander
laander

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:

  1. 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.

  2. 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:

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

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

Upvotes: 203

haccks
haccks

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

vlatko606
vlatko606

Reputation: 1149

Creation of the new Credentials key under:

enter image description here

Was the honey for me.

from:

enter image description here

to:

enter image description here

Upvotes: 2

Seth Samuel
Seth Samuel

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

Khribi Wessim
Khribi Wessim

Reputation: 722

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

Upvotes: 0

Fetchinator7
Fetchinator7

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

Oleksii Kyslytsyn
Oleksii Kyslytsyn

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

AlexeyVMP
AlexeyVMP

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

thisismydesign
thisismydesign

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

Kevin Murphy
Kevin Murphy

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:

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

enter image description here

I hope this saves someone some time!

Upvotes: 11

Kashif Tufail
Kashif Tufail

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

AlbertS
AlbertS

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

Jason Sultana
Jason Sultana

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

sparkyspider
sparkyspider

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

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

nathanfranke
nathanfranke

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

Subrata Fouzdar
Subrata Fouzdar

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

Kate Kasinskaya
Kate Kasinskaya

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

DIMMACK
DIMMACK

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

Waqleh
Waqleh

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

Kanishk Gupta
Kanishk Gupta

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

Hisham Javed
Hisham Javed

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.

  1. Request header must contain "content-type: application/x-www-form-urlencoded"
  2. 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

Upvotes: 4

Lindauson
Lindauson

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

paul
paul

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

Justin Fiedler
Justin Fiedler

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

Related Questions