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'
}
/**
* 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>",
);