What I have so far is the app redirects to the consent page. The user accepts, then I'm redirected back to localhost with a valid authorization code. From what I understand, I need to make another call and exchange this code for an access token. getAccessToken()
is not working, however. The console log is returning this:
invalid_client
invalid_request
Please let me know which additional information is needed.
Here's the relevant code:
var { google } = require('googleapis');
var http = require("http");
var request = require('request');
var oauth2Client = new google.auth.OAuth2(
'<My Client ID>',
'<My Client Secret>',
'http://localhost:8080'
);
exports.generateAuthCodeUrl = function () {
const url = oauth2Client.generateAuthUrl({
access_type: 'offline',
scope: ''
});
return url;
};
exports.getAccessToken = function (accessCode) {
var codeOptions = {
code: accessCode
}
oauth2Client.getToken(codeOptions, function (err, tokens) {
// Now tokens contains an access_token and an optional refresh_token. Save them.
if (!err) {
oauth2Client.setCredentials(tokens);
return tokens;
}
console.log(err.message);
});
};
Edit: Summary and what worked for me
I read the linked article from pinoyyid's answer TWICE and also noted the steps listed in his answer. Listing the simple steps helped me understand more clearly. Also, as recommended in the comments, I removed the googleapi library (The error mentioned above was occurring within the code of this library) and just made regular calls to the necessary endpoints with the request
library. I used request
because it's much less verbose. The code that I ended up with looks like this:
exports.generateAuthCodeUrl = function () {
var authURL = "?" +
"client_id=" + client_id +
"&scope=" + scope +
"&redirect_uri=" + redirect_uri +
"&response_type=" + response_type;
//redirect to consent page
return authURL;
};
exports.getAccessToken = function (x) {
var postDataUrl = '?' +
'code=' + x + //auth code received from the previous call
'&client_id=' + client_id +
'&client_secret=' + client_secret +
'&redirect_uri=' + redirect_uri +
'&grant_type=' + "authorization_code"
var options = {
uri: postDataUrl,
method: 'POST'
};
request(options, function (err, res, body) {
return body; //returns an object with an access token!!!
});
};
Very glad I got this working!! Thank you all so much
What I have so far is the app redirects to the consent page. The user accepts, then I'm redirected back to localhost with a valid authorization code. From what I understand, I need to make another call and exchange this code for an access token. getAccessToken()
is not working, however. The console log is returning this:
invalid_client
invalid_request
Please let me know which additional information is needed.
Here's the relevant code:
var { google } = require('googleapis');
var http = require("http");
var request = require('request');
var oauth2Client = new google.auth.OAuth2(
'<My Client ID>',
'<My Client Secret>',
'http://localhost:8080'
);
exports.generateAuthCodeUrl = function () {
const url = oauth2Client.generateAuthUrl({
access_type: 'offline',
scope: 'https://www.googleapis.com/auth/blogger'
});
return url;
};
exports.getAccessToken = function (accessCode) {
var codeOptions = {
code: accessCode
}
oauth2Client.getToken(codeOptions, function (err, tokens) {
// Now tokens contains an access_token and an optional refresh_token. Save them.
if (!err) {
oauth2Client.setCredentials(tokens);
return tokens;
}
console.log(err.message);
});
};
Edit: Summary and what worked for me
I read the linked article from pinoyyid's answer TWICE and also noted the steps listed in his answer. Listing the simple steps helped me understand more clearly. Also, as recommended in the comments, I removed the googleapi library (The error mentioned above was occurring within the code of this library) and just made regular calls to the necessary endpoints with the request
library. I used request
because it's much less verbose. The code that I ended up with looks like this:
exports.generateAuthCodeUrl = function () {
var authURL = "https://accounts.google.com/o/oauth2/v2/auth?" +
"client_id=" + client_id +
"&scope=" + scope +
"&redirect_uri=" + redirect_uri +
"&response_type=" + response_type;
//redirect to consent page
return authURL;
};
exports.getAccessToken = function (x) {
var postDataUrl = 'https://www.googleapis.com/oauth2/v4/token?' +
'code=' + x + //auth code received from the previous call
'&client_id=' + client_id +
'&client_secret=' + client_secret +
'&redirect_uri=' + redirect_uri +
'&grant_type=' + "authorization_code"
var options = {
uri: postDataUrl,
method: 'POST'
};
request(options, function (err, res, body) {
return body; //returns an object with an access token!!!
});
};
Very glad I got this working!! Thank you all so much
Share Improve this question edited Jun 12, 2019 at 14:11 Bill Kindig asked Jan 8, 2019 at 17:53 Bill KindigBill Kindig 3,6222 gold badges32 silver badges45 bronze badges 5- if you can use passport.js then let me know.. i can help – programoholic Commented Jan 10, 2019 at 19:06
- @programoholic Currently looking for a pure node answer. But may update in the future if we go the express and passport route. – Bill Kindig Commented Jan 10, 2019 at 19:48
- 1 my 2c, throw away the library and call the endpoints directly. It's one redirect to get the auth code, then a simple REST call to exchange that for an Access/Refresh Token. If you have any problems you can compare your http request with that from the Google OAuth playground to see what you are getting wrong. – pinoyyid Commented Jan 11, 2019 at 19:02
- @pinoyyid That actually sounds like a good idea. I will try without the library. – Bill Kindig Commented Jan 11, 2019 at 21:23
- In case it helps, I've posted the Dummy's Guide to Oauth as an answer to set out the steps. – pinoyyid Commented Jan 11, 2019 at 22:19
2 Answers
Reset to default 15 +50The Dummy's Guide to 3-legged Google OAuth.
Literally everything you need to know is on this single page https://developers.google.com/identity/protocols/OAuth2WebServer . Read it twice and you'll be an OAuth ninja. In summary, it says ...
- Construct an accounts.google.com URL with 4 query params :-
client_id
to identify your appscope
to say what permissions you're asking forredirect_uri
to tell Google where to redirect the user's browser with the resultresponse_type=code
to say you want an Auth Code
- redirect the user's browser to that URL
- Have a sip of coffee while the user logs in, chooses his Google account, and grants permission, until eventually ...
- The user's browser gets redirected back to your app's
redirect_uri
, with a query param ofcode
which is the one-time Auth Code - Post the Auth Code to Google's token endpoint
- Parse the JSON response to get the Access Token
- Use the Access Token in a "authorization: bearer access_token" http header for your subsequent Google API requests
If you go to https://developers.google.com/oauthplayground/ you can run through the steps online to see what the various URLs and responses look like.
I wrote this library to get user information, hope this helps.
'use strict'
const { google } = require('googleapis')
const credentials = require('../configs/config').google
class googleApi {
constructor(){
const {client_id, client_secret, redirectUri } = credentials;
this.oAuth2Client = new google.auth.OAuth2(client_id, client_secret, redirectUri)
}
generateUrl(scopes){
const url = this.oAuth2Client.generateAuthUrl({
access_type: 'offline',
scope: scopes.join(' ')
})
return url;
}
async getUserInfo(code){
const credentials = await this.oAuth2Client.getToken(code)
this.oAuth2Client.setCredentials(credentials.tokens);
const plus = google.plus({
version: 'v1',
auth: this.oAuth2Client,
});
const data = await plus.people.get({userId: 'me'});
return data;
}
}
module.exports = new googleApi();
and this is the implementation:
'use strict'
const googleApi = require('../libs/google');
exports.requestGmailAuth = function (req, res, next){
let url = googleApi.generateUrl(scopes)
res.redirect(url);
}
exports.getGmailUserInfo = async function (req, res, next){
const qs = new url.URL(req.url, 'http://localhost:3000').searchParams;
let code = qs.get('code')
if(!code){
next(new Error('No code provided'))
}
googleApi.getUserInfo(code)
.then(function(response){
res.send(response.data)
}).catch(function(e){
next(new Error(e.message))
})
}
these are the routes:
app.get('/request/gmail/auth', user.requestGmailAuth)
app.get('/get/gmail/user', user.getGmailUserInfo)
When /request/gmail/auth receives a request it redirects to consent page then the consent page redirects to /get/gmail/user with the "code" parameter.
try this snipet and if the problem persists check your client id and client secret, and also make sure that you have google plus api enabled in your developer dashboard.