I have used the following guides
https://developers.google.com/identity/sign-in/web/server-side-flow
https://developers.google.com/calendar/quickstart/nodejs
googleapis version: 39.2.01> I have a react frontend website that produces a google button using npm module gapi-script. I get the thought to the authorization and the client receives the code as per https://developers.google.com/identity/sign-in/web/server-side-flow.
2> I then send the access code to a graphql end point which then gets stored in a DB.
3> I then have a nodejs app that reads this value out and useses it. I have a modified version of https://developers.google.com/calendar/quickstart/nodejs This is shown below.
import fs from "fs";
import { google } from "googleapis";
import readline from "readline";
export function authorize(credentials: any, accessData: any, callback: any) {
const {client_secret, client_id, redirect_uris} = credentials.web;
const oAuth2Client = new google.auth.OAuth2(client_id, client_secret, redirect_uris[0]);
// Check if we have previously stored a token.
console.log("what is accessData at authorization stage - " + JSON.stringify(accessData));
const token = accessData.Item.access_token;
console.log("what is token at authorization stage - " + JSON.stringify(token));
if (!token) {
return getAccessToken(oAuth2Client, accessData, callback);
}
oAuth2Client.setCredentials(token);
callback(oAuth2Client);
}
export function getAccessToken(oAuth2Client: any, accessData: any, callback: any) {
console.log("what is token at authorization stage - " + JSON.stringify(accessData.Item.auth_token));
const test = "XXXXXXXXXXXXXX";
oAuth2Client.getToken(test, (err: any, token: any) => {
if (err) { return console.error("Error retrieving access token", err); }
console.log("credentials parsed back from Google - " + JSON.stringify(token));
oAuth2Client.setCredentials(token);
// Store the token to synamodb for later program executions
callback(oAuth2Client);
});
}
export function listEvents(auth: any) {
const calendar = google.calendar({version: "v3", auth});
calendar.events.list({
calendarId: "primary",
maxResults: 10,
orderBy: "startTime",
singleEvents: true,
timeMin: (new Date()).toISOString(),
}, (err: any, res: any) => {
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: any, i: any) => {
const start = event.start.dateTime || event.start.date;
console.log(`${start} - ${event.summary}`);
});
} else {
console.log("No upcoming events found.");
}
});
}
5> I get the response. For obvious resons I blanked out the security information with "XXXXXXXXXX"
config: {
method: 'POST',
url: 'https://oauth2.googleapis.com/token',
data: 'code="XXXXXXX"client_id="XXXXXXX"&client_secret="XXXXXXX"&redirect_uri="XXXXXXX"&grant_type=authorization_code&code_verifier=',
headers: [Object],
params: [Object: null prototype] {},
paramsSerializer: [Function: paramsSerializer],
body: 'code="XXXXXXX"&client_id="XXXXXXX"&client_secret="XXXXXXX"&redirect_uri="XXXXXXX"&grant_type=authorization_code&code_verifier=',
validateStatus: [Function: validateStatus],
responseType: 'json'
},
data: { error: 'invalid_grant', error_description: 'Bad Request' },
headers: {
'alt-svc': 'quic=":443"; ma=2592000; v="46,43",h3-Q050=":443"; ma=2592000,h3-Q049=":443"; ma=2592000,h3-Q048=":443"; ma=2592000,h3-Q046=":443"; ma=2592000,h3-Q043=":443"; ma=2592000',
'cache-control': 'private',
connection: 'close',
'content-encoding': 'gzip',
'content-type': 'application/json; charset=utf-8',
date: 'Wed, 29 Jan 2020 21:54:53 GMT',
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="XXXXXXX"&client_id="XXXXXXX"&client_secret="XXXXXXX"&redirect_uri="XXXXXXX"&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="XXXXXXX"&client_id="XXXXXXX"&client_secret="XXXXXXX"&redirect_uri="XXXXXXX"&grant_type=authorization_code&code_verifier=',
validateStatus: [Function: validateStatus],
responseType: 'json'
},
code: '400'
}
Notes
I notice the following that suggests something is wrong.
1> code_verifier= is blank at the end of the data field response that comes back. Either I am not setting something or I am using the wrong client type.
2> I am miss understanding how this library works is wrong. that actually I need to request the access/refresh token straight away in the client and send the refresh/access token to my backend for future use.
That I can't just store the access code in a database and call it later on to perform actions.
Thanks!
okay my understanding is that I need to use the same clientID for this flow, I have now changed my setup so that the clientid used in react-frontend is the same as the one in the backend.
I still get the error, however I am trying to find the equiviland code -
GoogleTokenResponse tokenResponse =
new GoogleAuthorizationCodeTokenRequest(
new NetHttpTransport(),
JacksonFactory.getDefaultInstance(),
"https://oauth2.googleapis.com/token",
clientSecrets.getDetails().getClientId(),
clientSecrets.getDetails().getClientSecret(),
authCode,
REDIRECT_URI) // Specify the same redirect URI that you use with your web
// app. If you don't have a web version of your app, you can
// specify an empty string.
.execute();
in the gapi library
the call oAuth2Client.getToken( does not seem to be eqivilant)
Also noticed this - https://stackoverflow.com/questions/57597327/exchanging-authorization-code-for-access-token-with-googleapis-package-in-node
so people seem to have experienced the same problem
For all thows that come after me - Resolution
Prerequisites
If you have the same setup as me where your performing the exchange for to token not in the frontend browser react code but in a back end setup. I did the following things which seems to resolve the issue.
1> Add the secondary hostname/port in the Google Cloud platform > APIs& Services > Credentials> (your oauth ClientID.) > Authorised JavaScript origins. with your web apps URI.
for example, if you have localhost:3000 serving your react code and localhost:3002. You need to ad both. This applies to your production setup as well.
2> The authorization code is one time use only. Once you use it, if your did not store the refresh token/access token. You need to ask the user to auth again. It gives the precise same message.
3> There seems to be a time out on how long you can hold the Authorization token. Google can answer this better than me, however I noticed I got the same error if I stored a working legitimate Authorization token for more than an hour and it gave me the same message as above.
My advise is send back better erroring than data: { error: 'invalid_grant', error_description: 'Bad Request' },
Since all the above scenarios seem to have caused the same error. It was hard for me to debug.
So me and a friend were facing exactly the same issue as everyone in this thread, and we were already going crazy after checking 10x every single param but still ending up with a invalid_grant error...
We were also using this library with callbacks, just like this example:
OAuthClient.getToken(code, (error, response) => {
if (error) return console.log('error', error);
console.log('response', response);
});
Turns out that, after almost giving up, we gave a shot trying using promises (using async/await) instead of callbacks...
And it actually worked! Yup, miraculously...
After seeing it working with promises, we moved back to callbacks, just to confirm, and BOOM! there was that despicable invalid_grant, again!
We permanently switched back to async/await and this is now solved for us :)
This might not work for everyone, but it worked for us... heres an example of the working code:
try {
const response = await OAuthClient.getToken(code);
console.log('response', response);
} catch (error) {
console.log('error', error);
}
I'll update this reply with the current googleapis package version we're using as soon as possible.
@t5unamie would you have a sample of your solution? I have been struggling with this for a while now. without success. thanks a lot