Google Calendar API "invalid_grant" error "Bad Request"

30 views
Skip to first unread message

Jhon Idrovo

unread,
Jun 21, 2024, 6:27:22 AMJun 21
to Google Calendar API
Hi everyone, I'm trying to make an application that
1. Uses login with firebase using Google as auth provider
2. Then I send the refresh token to the backend to access the google calendar of the logged in user
3. When trying to do that I get the following error
GaxiosError: invalid_grant
    at Gaxios._request (/Users/jhonidrovo/WevDev/ASTRO/astro-monorepo/astro-backend/node_modules/gaxios/build/src/gaxios.js:142:23)
    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
    at async UserRefreshClient.refreshTokenNoCache (/Users/jhonidrovo/WevDev/ASTRO/astro-monorepo/astro-backend/node_modules/google-auth-library/build/src/auth/oauth2client.js:212:19)
    at async UserRefreshClient.getRequestMetadataAsync (/Users/jhonidrovo/WevDev/ASTRO/astro-monorepo/astro-backend/node_modules/google-auth-library/build/src/auth/oauth2client.js:333:17)
    at async UserRefreshClient.requestAsync (/Users/jhonidrovo/WevDev/ASTRO/astro-monorepo/astro-backend/node_modules/google-auth-library/build/src/auth/oauth2client.js:418:23)
    at async listCalendarEvents (/Users/jhonidrovo/WevDev/ASTRO/astro-monorepo/astro-backend/dist/controllers/calendarController.js:98:23)
    at async getCalendarData (/Users/jhonidrovo/WevDev/ASTRO/astro-monorepo/astro-backend/dist/controllers/calendarController.js:118:20) {
  config: {
    retry: true,
    retryConfig: {
      httpMethodsToRetry: [ 'GET', 'PUT', 'POST', 'HEAD', 'OPTIONS', 'DELETE' ],
      currentRetryAttempt: 0,
      retry: 3,
      noResponseRetries: 2,
      statusCodesToRetry: [ [ 100, 199 ], [ 408, 408 ], [ 429, 429 ], [ 500, 599 ] ]
    },
    method: 'POST',
    url: 'https://oauth2.googleapis.com/token',
    data: '<<REDACTED> - See `errorRedactor` option in `gaxios` for configuration>.',
    headers: {
      'Content-Type': 'application/x-www-form-urlencoded',
      'User-Agent': 'google-api-nodejs-client/9.11.0',
      'x-goog-api-client': 'gl-node/20.11.1'
    },
    paramsSerializer: [Function: paramsSerializer],
    body: '<<REDACTED> - See `errorRedactor` option in `gaxios` for configuration>.',
    validateStatus: [Function: validateStatus],
    responseType: 'unknown',
    errorRedactor: [Function: defaultErrorRedactor]
  },
  response: {
    config: {
      retry: true,
      retryConfig: {
        httpMethodsToRetry: [ 'GET', 'PUT', 'POST', 'HEAD', 'OPTIONS', 'DELETE' ]
      },
      method: 'POST',
      url: 'https://oauth2.googleapis.com/token',
      data: '<<REDACTED> - See `errorRedactor` option in `gaxios` for configuration>.',
      headers: {
        'Content-Type': 'application/x-www-form-urlencoded',
        'User-Agent': 'google-api-nodejs-client/9.11.0',
        'x-goog-api-client': 'gl-node/20.11.1'
      },
      paramsSerializer: [Function: paramsSerializer],
      body: '<<REDACTED> - See `errorRedactor` option in `gaxios` for configuration>.',
      validateStatus: [Function: validateStatus],
      responseType: 'unknown',
      errorRedactor: [Function: defaultErrorRedactor]
    },
    data: { error: 'invalid_grant', error_description: 'Bad Request' },
    headers: {
      'alt-svc': 'h3=":443"; ma=2592000,h3-29=":443"; ma=2592000',
      'cache-control': 'no-cache, no-store, max-age=0, must-revalidate',
      'content-encoding': 'gzip',
      'content-type': 'application/json; charset=utf-8',
      date: 'Fri, 21 Jun 2024 10:16:16 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',
    request: { responseURL: 'https://oauth2.googleapis.com/token' }
  },
  error: undefined,
  status: 400,
  [Symbol(gaxios-gaxios-error)]: '6.6.0'
}
The code looks like this:
/**
 * Reads previously authorized credentials from the save file.
 */
async function loadSavedCredentialsIfExist() {
    try {
        const content = await fs.readFile(TOKEN_PATH);
        const credentials = JSON.parse(content);

        // Exchange the refresh token for a new access token
        const response = await fetch(
            "https://securetoken.googleapis.com/v1/token?key=<my api key>",
            {
                method: "POST",
                headers: {
                    "Content-Type": "application/x-www-form-urlencoded",
                },
                body: `grant_type=refresh_token&refresh_token=${credentials.refresh_token}`,
            },
        );
        const data = await response.json();

        return google.auth.fromJSON({
            ...credentials,
            refresh_token: data.refresh_token,
            access_token: data.access_token,
        });
    } catch (err) {
        console.log(err);

        return null;
    }
}

/**
 * Serializes credentials to a file compatible with GoogleAuth.fromJSON.
 */
async function saveCredentials(client: { refreshToken: string }) {
    const content = await fs.readFile(CREDENTIALS_PATH);
    const keys = JSON.parse(content);
    const key = keys.installed || keys.web;
    const payload = JSON.stringify({
        type: "authorized_user",
        client_id: key.client_id,
        client_secret: key.client_secret,
        refresh_token: client.refreshToken,
    });
    await fs.writeFile(TOKEN_PATH, payload);
}

/**
 * Load or request or authorization to call APIs.
 *
 */
async function authorize(args?: { refreshToken: string }) {
    let client = await loadSavedCredentialsIfExist();
    if (client) {
        return client;
    }
    await saveCredentials({ refreshToken: args.refreshToken });
    client = await loadSavedCredentialsIfExist();
    return client;
}
    async function listCalendarEvents(refreshToken: string) {
        try {
            // Exchange the refresh token for an access token
            const response = await fetch(
                "https://securetoken.googleapis.com/v1/token?key=<my api key>",
                {
                    method: "POST",
                    headers: {
                        "Content-Type": "application/x-www-form-urlencoded",
                    },
                    body: `grant_type=refresh_token&refresh_token=${refreshToken}`,
                },
            );

            if (!response.ok) {
                const errorText = await response.text();
                throw new Error(`Error exchanging refresh token: ${errorText}`);
            }
            const data = await response.json();
            const credentials = await authorize({
                refreshToken: data.refresh_token,
            });
            console.log("Access Token:", data.access_token);

            const a = new google.auth.GoogleAuth({ authClient: credentials });
            const calendar = google.calendar({
                version: "v3",
                auth: a,
            });
            const r = await calendar.events.list({
                calendarId: "primary",
                timeMin: new Date().toISOString(),
                singleEvents: true,
                orderBy: "startTime",
                timeMax: new Date(Date.now() + 604800000).toISOString(),
                key: "<my api key>",
            });

            console.log("Calendar Events:", r.data.items);
            return r.data.items;
        } catch (error) {
            console.error("Failed to list calendar events:", error.message);
            throw error;
        }
    }
    const events = await listCalendarEvents(
        "<refresh token from firebase login>",
    );
Reply all
Reply to author
Forward
0 new messages