Google token endpoint returning unsupported grant type - node.js

I'm trying to implement Google Oauth to get information on users who sign in with Google such as their name and profile photo.
I found this react library which seems like it can get me a code when using the flow: 'auth_code' param.
Additionally I've found these Google docs which outline how to exchange a code for an ID token which will have user information.
I can successfully retrieve a code using the react component as shown in this slightly redacted log message from my browser:
{
"code": "4/0AWtgzh4VK3QT85egP0m9Rsau_gND-FPDxpM-cPB7ocxTfNkZSCzFSbE86<redacted suffix>",
"scope": "email profile openid https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/userinfo.email",
"authuser": "0",
"prompt": "consent"
}
I then send this code to my backend which fires off the following request to the token endpoint as found in the Discovery document, mentioned in the OpenID Google docs:
try {
const resp = await axios.post("https://oauth2.googleapis.com/token", {
code: googleJWT,
client_id: getGoogleClientId(),
client_secret: googleSecret,
redirect_uri: "http://localhost:3000",
grant_type: "authorization_code"
}, {
headers: {
"Content-Type": "application/x-www-form-urlencoded"
}
})
console.log("################## posted", resp)
} catch (e) {
console.log("################## could not post")
console.log(e)
}
However, this results in an error being thrown:
error: 'unsupported_grant_type',
error_description: 'Invalid grant_type: '
even though a grant type of authorization_code is reported as supported by the Discovery document.
For completeness, here's the redacted axios config that gets printed with the above error message:
config: {
transitional: {
silentJSONParsing: true,
forcedJSONParsing: true,
clarifyTimeoutError: false
},
adapter: [Function: httpAdapter],
transformRequest: [ [Function: transformRequest] ],
transformResponse: [ [Function: transformResponse] ],
timeout: 0,
xsrfCookieName: 'XSRF-TOKEN',
xsrfHeaderName: 'X-XSRF-TOKEN',
maxContentLength: -1,
maxBodyLength: -1,
validateStatus: [Function: validateStatus],
headers: {
Accept: 'application/json, text/plain, */*',
'Content-Type': 'application/x-www-form-urlencoded',
'User-Agent': 'axios/0.26.1',
'Content-Length': 298
},
method: 'post',
url: 'https://oauth2.googleapis.com/token',
data: '{"code":"4/0AWtgzh4VK3QT85egP0m9Rsau_gND-FPDxpM-cPB7ocxTfNkZSCzFSbE86<redacted>","client_id":"221274282648-q9nrm8j61s5cba218qot926vnloua2hb.apps.googleusercontent.com","client_secret":"<redacted>","redirect_uri":"http://localhost:3000","grant_type":"authorization_code"}'
},
Why is google complaining that the provided grant type isn't supported and how can I swap this code for an ID token?

Related

Google picker on frontend (user login in reactJS) and google drive download on backend (node.js), API v3

I am building a file uploader which provides the user an option to upload files from his google drive. Google picker is set up and working on the frontend (reactJS) and I have the fileID and OAuth token given by Google Picker. I send these to the backend (node.js) and have the Google Drive API over there. I followed the documentation https://developers.google.com/drive/api/v3/manage-downloads and put the oauth token in the auth param in drive.files.get, now I got the following error
Error GaxiosError: [object Object]
at Gaxios._request (/home/.../node_modules/gaxios/src/gaxios.ts:112:15)
at processTicksAndRejections (internal/process/task_queues.js:97:5) {
response: {
config: {
url: 'https://www.googleapis.com/drive/v3/files/1GqxjzMjrDdJquOPeMrFGIMngE20vTrjU?alt=media&key=ya29.6q33c6T418VuSILwq...cLKnBMKEG4vhui8K',
method: 'GET',
responseType: 'stream',
userAgentDirectives: [Array],
paramsSerializer: [Function],
headers: [Object],
params: [Object],
validateStatus: [Function],
retry: true,
retryConfig: [Object]
},
data: PassThrough {
_readableState: [ReadableState],
readable: true,
_events: [Object: null prototype],
_eventsCount: 2,
_maxListeners: undefined,
_writableState: [WritableState],
writable: false,
allowHalfOpen: true,
_transformState: [Object],
[Symbol(kCapture)]: false
},
headers: {
'alt-svc': 'h3-29=":443"; ma=2592000,h3-T051=":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': 'private, max-age=0',
connection: 'close',
'content-length': '176',
'content-type': 'application/json; charset=UTF-8',
date: 'Wed, 16 Dec 2020 11:42:15 GMT',
expires: 'Wed, 16 Dec 2020 11:42:15 GMT',
server: 'UploadServer',
vary: 'Origin, X-Origin',
'x-guploader-uploadid': 'ABg5-Uw8z7O1Hpe1od4_dQF9So652TfYS0Mc1vpIu3t4DDXPzB7YvNwQAeHKCvoNBF-7m_pW9e8EHPOgrEHS84HWR7M'
},
status: 400,
statusText: 'Bad Request',
request: {
responseURL: 'https://www.googleapis.com/drive/v3/files/1GqxjzMjrDdJquOPeMrFGIMngE20vTrjU?alt=media&key=ya29.6q33c6T418VuSILwq...cLKnBMKEG4vhui8K'
}
},
config: {
url: 'https://www.googleapis.com/drive/v3/files/1GqxjzMjrDdJquOPeMrFGIMngE20vTrjU?alt=media&key=ya29.6q33c6T418VuSILwq...cLKnBMKEG4vhui8K',
method: 'GET',
responseType: 'stream',
userAgentDirectives: [ [Object] ],
paramsSerializer: [Function],
headers: {
'x-goog-api-client': 'gdcl/4.4.3 gl-node/12.19.0 auth/6.1.3',
'Accept-Encoding': 'gzip',
'User-Agent': 'google-api-nodejs-client/4.4.3 (gzip)'
},
params: {
alt: 'media',
key: 'ya29.6q33c6T418VuSILwq...cLKnBMKEG4vhui8K'
},
validateStatus: [Function],
retry: true,
retryConfig: {
currentRetryAttempt: 0,
retry: 3,
httpMethodsToRetry: [Array],
noResponseRetries: 2,
statusCodesToRetry: [Array]
}
},
code: '400'
}
Here I observed that, there's a "key" parameter but from what I understand there should be an Authorization token. I tried using the file url from POSTMAN using a GET call with Authorization Bearer but it responded with a sign in page.
Questions:
Since I provided a token, shouldn't it download the file directly?
Why isn't the file downloading on the backend?
Note: I did follow the google drive api documentation on node.js but even that prompts user sign in which shouldn't be the case.
Edit: Added the client code as well
Client side (reactjs)
<GooglePicker
clientId={"7...b.apps.googleusercontent.com"}
developerKey={"AIz...PY"}
scope={["https://www.googleapis.com/auth/drive.readonly"]}
onChange={handleFileChange}
onAuthenticate={(token) => setDownloadToken(token)}
onAuthFailed={(data) => console.log("on auth failed:", data)}
multiselect={false}
navHidden={false}
authImmediate={false}
query={"a query string like .txt or fileName"}
viewId={"PDFS"}
>
<Button variant="contained" size="small">
G-Drive
</Button>
</GooglePicker>{" "}
Node.js Code
export const googleDriveUpload = async (_, {fileID, authToken}) => {
console.log(fileID, authToken);
const drive = google.drive({version: 'v3', auth:authToken});
var dest = fs.createWriteStream('/tmp/resume.pdf');
drive.files.get({fileId: fileID, alt: 'media'},
{responseType: 'stream'}, (err, res ) => {
if (err) {
console.log("Error " ,err);
} else {
res.data.pipe(file);
console.log("Download complete");
}
});
When you define const drive = google.drive({version: 'v3', auth:XXX}), you need to assign to auth the response of the function authorize(), as shown in the quickstart for Drive API in node.js
Please follow the complete quickstart to obtain a valid authenticated client
If creating authenticating with an oAuth2 client is not what you want, there are also other options to create valid crendetials, see google-api-nodejs-client library

Error: read ECONNRESET Axios in backend Node js

I am using axios in the backend nodejs and wanted to fetch the data of the movie database API.
when is send request from frontend to my nodejs API it gives Error: read ECONNRESET error. But sometimes it works properly. here is my API route :-
app.get("/api/v1/movie/details/:movieId",(req,res) => { const id = req.params.movieId;
let results = {};
axios
.get(
`/movie/${id}?api_key=${process.env.API_KEY}&language=en-US&append_to_response=videos,credits`
)
.then((response) => {
results = { ...response.data };
return res.status(200).json(results);
})
.catch((e) => {
console.log(e);
return res.status(500).json(e.message);
});});
And here is the error I am getting in console
Error: read ECONNRESET
at TLSWrap.onStreamRead (internal/stream_base_commons.js:205:27) {
errno: 'ECONNRESET',
code: 'ECONNRESET',
syscall: 'read',
config: {
url: '/movie/556984?api_key=API_KEY&language=en-US&append_to_response=videos,credits',
method: 'get',
headers: {
Accept: 'application/json, text/plain, */*',
'User-Agent': 'axios/0.20.0'
},
baseURL: 'https://api.themoviedb.org/3',
transformRequest: [ [Function: transformRequest] ],
transformResponse: [ [Function: transformResponse] ],
timeout: 0,
adapter: [Function: httpAdapter],
xsrfCookieName: 'XSRF-TOKEN',
xsrfHeaderName: 'X-XSRF-TOKEN',
maxContentLength: -1,
maxBodyLength: -1,
validateStatus: [Function: validateStatus],
data: undefined
},
I don't understand how sometimes it working but sometimes its showing this error.
But whenever I send request to directly to the movie database API from the browser (not sending to my own API) i got response everytime... please help me !!!

Trying to implement NodeJS Gmail API example

I am trying to get the NodeJs Gmail API Quickstart example working. At first, I was able to get the example running and receive an out put that looked like this.
INBOX
SPAM
ETC ...
I made a few changes after so that I could see messages, etc. Once that happened I started getting the following error after the prompt (It was working prior).
Authorize this app by visiting this url: https://accounts.google.com/o/oauth2/v2/auth?access_type=offline&scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fgmail.modify&response_type=code&client_id=***&redirect_uri=***
Enter the code from that page here:
I get this error code
Error retrieving access token GaxiosError: invalid_grant
at Gaxios.<anonymous> (C:\***\node_modules\gaxios\build\src\gaxios.js:73:27)
at Generator.next (<anonymous>)
at fulfilled (C:\***\node_modules\gaxios\build\src\gaxios.js:16:58)
at processTicksAndRejections (internal/process/task_queues.js:97:5) {
response: {
config: {
method: 'POST',
url: 'https://oauth2.googleapis.com/token',
data: 'code=***client_id=***&client_secret=***&redirect_uri=http***=authorization_code&code_verifier=',
headers: [Object],
params: [Object: null prototype] {},
paramsSerializer: [Function: paramsSerializer],
body: 'code=***&client_id=***&client_secret=***&redirect_uri=***&grant_type=authorization_code&code_verifier=',
validateStatus: [Function: validateStatus],
responseType: 'json'
},
data: {
error: 'invalid_grant',
error_description: 'Malformed auth code.'
},
headers: {
***,
***
},
status: 400,
statusText: 'Bad Request'
},
config: {
method: 'POST',
url: 'https://oauth2.googleapis.com/token',
data: 'code=***&client_id=***&client_secret=***&redirect_uri=***=authorization_code&code_verifier=',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'User-Agent': 'google-api-nodejs-client/3.1.2',
},
params: [Object: null prototype] {},
paramsSerializer: [Function: paramsSerializer],
body: 'code=***&client_id=***&client_secret=***&redirect_uri=***&grant_type=authorization_code&code_verifier=',
validateStatus: [Function: validateStatus],
responseType: 'json'
},
code: '400'
}
So I reverted the changes back to the original quickstart code, and I am still getting the above error. I have no idea why this is happening and would appreciate some help!
the taken of Gmail api expires really soon after you work with it
try to delete the credentials.json and start the script again .

Implementing facebook login with nodeJS

I am implementing Facebook login in my Vue (nuxt) app with NodeJS backend.
I am able to receive my auth code, but am unable to get the access token using that. It keeps returning me the following error:
data: {
error: {
message: 'Missing redirect_uri parameter.',
type: 'OAuthException',
code: 191,
fbtrace_id: 'AEzwwqCBEvvkq9mDQPrvc-7'
}
}
This is the code I have:
My page containing the facebook login button:
facebookLoginUrl () {
const stringifiedParams = queryString.stringify({
client_id: process.env.facebook_app_id,
redirect_uri: process.env.base_url + this.$findPath('/authenticatie/facebook'),
scope: ['public_profile', 'email', 'user_birthday', 'user_location', 'user_hometown'].join(','), // comma seperated string
response_type: 'code',
auth_type: 'rerequest',
display: 'popup',
})
return `https://www.facebook.com/v4.0/dialog/oauth?${stringifiedParams}`
}
The page where my redirect_uri is going to only contains the following in the created hook:
created () {
let facebookAuthCode = this.$route.query.code
let redirectUri = process.env.base_url + this.$findPath('/authenticatie/facebook')
if (facebookAuthCode) {
this.$getFromBackend('/getFacebookAccessTokenFromCode', { authCode: facebookAuthCode, redirectUri })
}
}
and finally my backend code does this:
axios.get('https://graph.facebook.com/v4.0/oauth/access_token', {
client_id: process.env.facebook_app_id,
client_secret: process.env.facebook_app_secret,
redirect_uri: encodeURIComponent(redirectUri),
code: encodeURIComponent(authCode),
})
.then(response => {
console.log(response)
})
.catch(error => {
console.log(error.response)
})
And it throws me the error mentioned above.
I can see in the error.response.config object the following:
config: {
url: 'https://graph.facebook.com/v4.0/oauth/access_token',
method: 'get',
headers: {
Accept: 'application/json, text/plain, */*',
'User-Agent': 'axios/0.19.2'
},
transformRequest: [ [Function: transformRequest] ],
transformResponse: [ [Function: transformResponse] ],
timeout: 0,
adapter: [Function: httpAdapter],
xsrfCookieName: 'XSRF-TOKEN',
xsrfHeaderName: 'X-XSRF-TOKEN',
maxContentLength: -1,
validateStatus: [Function: validateStatus],
client_id: 'xxxxxxxxxxxxxx',
client_secret: 'xxxxxxxxxxxxxxxxxx',
redirect_uri: 'http://localhost:3000/nl-be/authenticatie/facebook',
code: 'xxxxxxxxxxxxxxxxxxxxxxxxxxxx',
data: undefined
},
So the redirect_uri is set, whats going on?
UPDATE: I changed the axios code according to #CBroe answer:
axios.get('https://graph.facebook.com/v4.0/oauth/access_token', qs.stringify({
client_id: process.env.facebook_app_id,
client_secret: process.env.facebook_app_secret,
redirect_uri: encodeURIComponent(redirectUri),
code: encodeURIComponent(authCode),
}), {
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
})
.then(response => {
console.log(response)
})
.catch(error => {
console.log(error.response)
})
But i am getting the exact same error.

Axios request from express app missing arguments

Im trying to make a POST request to a FacePlusPlus API.
At the moment I'm just running app.js with node, but I keep getting an error .
My axios request inside express app:
axios({
method: "post",
url: "https://api-us.faceplusplus.com/facepp/v3/face/analyze",
headers: {
'Content-Type': "application/json",
},
data: {
'api_key': '<My api key goes here>',
'api_secret': '<My api secret goes here>',
'image_url': 'https://images.pexels.com/photos/220453/pexels-photo-220453.jpeg?w=1260&h=750&auto=compress&cs=tinysrgb'
}
})
.then(res => console.log(res))
.catch(err => console.log(err))
The Response error:
{ Error: Request failed with status code 400
at createError (/Users/Matt/webdev/test/node_modules/axios/lib/core/createError.js:16:15)
at settle (/Users/Matt/webdev/test/node_modules/axios/lib/core/settle.js:18:12)
at IncomingMessage.handleStreamEnd (/Users/Matt/webdev/test/node_modules/axios/lib/adapters/http.js:192:11)
at emitNone (events.js:110:20)
at IncomingMessage.emit (events.js:207:7)
at endReadableNT (_stream_readable.js:1047:12)
at _combinedTickCallback (internal/process/next_tick.js:102:11)
at process._tickCallback (internal/process/next_tick.js:161:9)
config:
{ adapter: [Function: httpAdapter],
transformRequest: { '0': [Function: transformRequest] },
transformResponse: { '0': [Function: transformResponse] },
timeout: 0,
xsrfCookieName: 'XSRF-TOKEN',
xsrfHeaderName: 'X-XSRF-TOKEN',
maxContentLength: -1,
validateStatus: [Function: validateStatus],
headers:
{ Accept: 'application/json, text/plain, */*',
'Content-Type': 'application/json',
'User-Agent': 'axios/0.17.1',
'Content-Length': 212 },
method: 'post',
url: 'https://api-us.faceplusplus.com/facepp/v3/face/analyze',
data: '{"api_key":"*** i removed this for question ***","api_secret":"*** i removed this for question ***","image_url":"https://images.pexels.com/photos/220453/pexels-photo-220453.jpeg?w=1260&h=750&auto=compress&cs=tinysrgb"}' }
...
...
data: { error_message: 'MISSING_ARGUMENTS: api_key' } } }
The data is obviously getting, sent, but why does it say missing argument?
Try console logging the api-key through the response and see what you get, since it is telling you it is a missing argument. Your data is a string and not an object should it be like that, just out of curiosity?

Resources