1

I'm trying to integrate google calendar in to my app, but getting an error: 'invalid_grant', error_description: 'Bad Request'

I've been following google documentation for the same and have referred to relevant StackOverflow posts to resolve the issue but no luck so far. The flow I'm implementing is as follows:

  1. generating a google consent url

     const {client_secret, client_id, redirect_uris} = credentials.web;
     const oAuth2Client = new google.auth.OAuth2(
         client_id, client_secret, redirect_uris[0]
     );
    
     const authUrl = oAuth2Client.generateAuthUrl({
         access_type: 'offline',
         scope: SCOPES,
         prompt: 'consent'
     });
     console.log('Authorize this app by visiting this url:', authUrl);
    
  2. after giving user consent, extracting the auth code from the URL and trying to get tokens in exchange of authcode

     const { client_secret, client_id, redirect_uris } = credentials.web;
    
     const OAuthtoClient = new google.auth.OAuth2(
     client_id, client_secret, redirect_uris[0]
     );
    
     let decoded = decodeURIComponent(code);
    
     OAuthtoClient.getToken(decoded, (err, token) => {
         if (err) return console.error('Error retrieving access token', err);
         console.log('Here the tokens :', token);
    

first I was getting another error { "error": "invalid_grant", "error_description": "Malformed auth code." }, which is solved referring to this solution. The code ran for once and I was able to generate "refresh_token and access_token" for the first time.

After which I tried to generate tokens for another user I got the following error error: 'invalid_grant', error_description: 'Bad Request'

I've tried things like resetting the client secret, but no luck.

My redirect URL are "redirect_uris": [ "https://example.com/authenticate-gcalendar", "http://localhost:3000" ]

origin URL "javascript_origins": [ "http://localhost:4000" ]

Scope const SCOPES = ['https://www.googleapis.com/auth/calendar', 'https://www.googleapis.com/auth/calendar.events'];

Thank you in advance!

Here is the full error message

Error retrieving access token GaxiosError: invalid_grant
at Gaxios.<anonymous> (F:\Git Clones\user-module\node_modules\gaxios\build\src\gaxios.js:73:27)
at Generator.next (<anonymous>)
at fulfilled (F:\Git Clones\user-module\node_modules\gaxios\build\src\gaxios.js:16:58)
at processTicksAndRejections (node:internal/process/task_queues:96:5) {
response: {
config: {
  method: 'POST',
  url: 'https://oauth2.googleapis.com/token',
  data: 'code=4%2F0AX4XfWiJdQtBAPFLwGHm6O5fotnjqYqHUSYzgUhvFpYyeQ7CziXcd_rc1f5bKMYJaJpklg&client_id&client_secret&redirect_uri=https%3A%2F%2Fexample.com%2Fauthenticate-gcalendar&grant_type=authorization_code&code_verifier=',
  headers: [Object],
  params: [Object: null prototype] {},
  paramsSerializer: [Function: paramsSerializer],
  body: 'code=4%2F0AX4XfWiJdQtBAPFLwGHm6O5fotnjqYqHUSYzgUhvFpYyeQ7CziXcd_rc1f5bKMYJaJpklg&client_id&client_secret&redirect_uri=https%3A%2F%2Fexample.com%2Fauthenticate-gcalendar&grant_type=authorization_code&code_verifier=',
  validateStatus: [Function: validateStatus],
  responseType: 'json'
},
data: { error: 'invalid_grant', error_description: 'Bad Request' },
headers: {
  'alt-svc': 'h3=":443"; ma=2592000,h3-29=":443"; ma=2592000,h3-Q050=":443"; ma=2592000,h3-Q046=":443"; ma=2592000,h3-Q043=":443"; ma=2592000,quic=":443"; ma=2592000; v="46,43"',
  'cache-control': 'no-cache, no-store, max-age=0, must-revalidate',
  connection: 'close',
  'content-encoding': 'gzip',
  'content-type': 'application/json; charset=utf-8',
  date: 'Mon, 13 Dec 2021 13:01:12 GMT',
  expires: 'Mon, 01 Jan 1990 00:00:00 GMT',
  pragma: 'no-cache',
  server: 'scaffolding on HTTPServer2',
  'transfer-encoding': 'chunked',
  vary: 'Origin, X-Origin, Referer',
  'x-content-type-options': 'nosniff',
  'x-frame-options': 'SAMEORIGIN',
  'x-xss-protection': '0'
   },
status: 400,
statusText: 'Bad Request'
},
 config: {
method: 'POST',
url: 'https://oauth2.googleapis.com/token',
data: 'code=4%2F0AX4XfWiJdQtBAPFLwGHm6O5fotnjqYqHUSYzgUhvFpYyeQ7CziXcd_rc1f5bKMYJaJpklg&client_id&client_secret&redirect_uri=https%3A%2F%2Fexample.com%2Fauthenticate-gcalendar&grant_type=authorization_code&code_verifier=',
headers: {
  'Content-Type': 'application/x-www-form-urlencoded',
  'User-Agent': 'google-api-nodejs-client/3.1.2',
  Accept: 'application/json'
},
params: [Object: null prototype] {},
paramsSerializer: [Function: paramsSerializer],
body: 'code=4%2F0AX4XfWiJdQtBAPFLwGHm6O5fotnjqYqHUSYzgUhvFpYyeQ7CziXcd_rc1f5bKMYJaJpklg&client_id&client_secret&redirect_uri=https%3A%2F%2Fexample.com%2Fauthenticate-gcalendar&grant_type=authorization_code&code_verifier=',
validateStatus: [Function: validateStatus],
responseType: 'json'
},
code: '400'
yuvraj singh
  • 88
  • 2
  • 7

2 Answers2

2

Invalid grant can be a hard error to diagnose. You should start by following the official Node.js quickstart

const fs = require('fs');
const readline = require('readline');
const {google} = require('googleapis');

// If modifying these scopes, delete token.json.
const SCOPES = ['https://www.googleapis.com/auth/calendar.readonly'];
// The file token.json stores the user's access and refresh tokens, and is
// created automatically when the authorization flow completes for the first
// time.
const TOKEN_PATH = 'token.json';

// Load client secrets from a local file.
fs.readFile('credentials.json', (err, content) => {
  if (err) return console.log('Error loading client secret file:', err);
  // Authorize a client with credentials, then call the Google Calendar API.
  authorize(JSON.parse(content), listEvents);
});

/**
 * Create an OAuth2 client with the given credentials, and then execute the
 * given callback function.
 * @param {Object} credentials The authorization client credentials.
 * @param {function} callback The callback to call with the authorized client.
 */
function authorize(credentials, callback) {
  const {client_secret, client_id, redirect_uris} = credentials.installed;
  const oAuth2Client = new google.auth.OAuth2(
      client_id, client_secret, redirect_uris[0]);

  // Check if we have previously stored a token.
  fs.readFile(TOKEN_PATH, (err, token) => {
    if (err) return getAccessToken(oAuth2Client, callback);
    oAuth2Client.setCredentials(JSON.parse(token));
    callback(oAuth2Client);
  });
}

/**
 * Get and store new token after prompting for user authorization, and then
 * execute the given callback with the authorized OAuth2 client.
 * @param {google.auth.OAuth2} oAuth2Client The OAuth2 client to get token for.
 * @param {getEventsCallback} callback The callback for the authorized client.
 */
function getAccessToken(oAuth2Client, callback) {
  const authUrl = oAuth2Client.generateAuthUrl({
    access_type: 'offline',
    scope: SCOPES,
  });
  console.log('Authorize this app by visiting this url:', authUrl);
  const rl = readline.createInterface({
    input: process.stdin,
    output: process.stdout,
  });
  rl.question('Enter the code from that page here: ', (code) => {
    rl.close();
    oAuth2Client.getToken(code, (err, token) => {
      if (err) return console.error('Error retrieving access token', err);
      oAuth2Client.setCredentials(token);
      // Store the token to disk for later program executions
      fs.writeFile(TOKEN_PATH, JSON.stringify(token), (err) => {
        if (err) return console.error(err);
        console.log('Token stored to', TOKEN_PATH);
      });
      callback(oAuth2Client);
    });
  });
}

/**
 * Lists the next 10 events on the user's primary calendar.
 * @param {google.auth.OAuth2} auth An authorized OAuth2 client.
 */
function listEvents(auth) {
  const calendar = google.calendar({version: 'v3', auth});
  calendar.events.list({
    calendarId: 'primary',
    timeMin: (new Date()).toISOString(),
    maxResults: 10,
    singleEvents: true,
    orderBy: 'startTime',
  }, (err, res) => {
    if (err) return console.log('The API returned an error: ' + err);
    const events = res.data.items;
    if (events.length) {
      console.log('Upcoming 10 events:');
      events.map((event, i) => {
        const start = event.start.dateTime || event.start.date;
        console.log(`${start} - ${event.summary}`);
      });
    } else {
      console.log('No upcoming events found.');
    }
  });
}
Linda Lawton - DaImTo
  • 106,405
  • 32
  • 180
  • 449
  • Thanks for the reply @Dalm I've been following the same documentation and have built my code over it and still getting the error. I thought [this solution](https://stackoverflow.com/a/19041051/13018216) could have solved my problem, but couldn't follow through as I was not able to find the "email" as mentioned in answer. – yuvraj singh Dec 13 '21 at 14:42
  • Such a bad solution, don't copy-paste and please explain. – Satyamskillz IN Aug 04 '22 at 01:51
  • I would be happy to what don't you understand? invalid grant can be caused by around 10 different things. currently the most common is the refresh token expiring after seven days. After that it's probably the server not being insync with NTP service. if you can give me the full error message I can try to help diagnosis your issue. – Linda Lawton - DaImTo Aug 04 '22 at 06:42
  • how about this one https://stackoverflow.com/a/48057303/1841839 – Linda Lawton - DaImTo Aug 04 '22 at 16:40
2

I was able to solve my problem

I was following the google documentation and was building my code over it, all the code was right but still the "invalid_grant"

In my case, I was redirecting google oauth to my app's url while running it to my localhost. After redirecting it to http://127.0.0.1:4000, the error was resolved.

yuvraj singh
  • 88
  • 2
  • 7
  • Can you show us what you changed? – pguardiario Mar 11 '22 at 00:05
  • the above solution would be resulted in a redirect miss match error and not an invalid grant error. – Linda Lawton - DaImTo Aug 04 '22 at 06:43
  • @pguardiario, sorry for the late reply initially I was redirecting google oauth to my webapp's public url (eg. "myapp.com/google-redirect" ) , but since I was generating consent url from localhost, I had to match the redirect url at the time of exchanging auth code for tokens ( to "localhost:4000/google-redirect" ), and hence my code worked. Hope this helps. – yuvraj singh Aug 08 '22 at 09:34