OAuth2 with Node, Express, and Request - node.js

I've been attempting to establish a simple Oauth2 flow using Node, Express, and Request, but I keep running into issues at the token request stage. In my specific example, I'm running an Oauth2 with MailChimp, but the problem persists with any platform.
In this example, after I post the code to get the token, I receive a response that tells me { error: 'invalid_request', error_description: 'Invalid grant_type parameter or parameter missing' }. That makes me think I'm not posting the data properly... I'm at the point that I'm convinced it's some core concept I'm missing in my logic. Any recommendations are appreciated.
Here's what I'm doing:
/* Redirect the user to MailChimp login */
app.get(`/login`, function (req, res) {
res.redirect(url.format({
pathname: 'https://login.mailchimp.com/oauth2/authorize/',
query: {
response_type: 'code',
client_id: client_id,
redirect_uri: 'http://127.0.0.1:3000/auth/'
}
}));
});
/* Recieve the code from MailChimp, and post it back to request a token */
app.get(`/auth`, function (req, res) {
request.post('https://login.mailchimp.com/oauth2/token', {
json: true,
gzip: true,
body: {
grant_type: 'authorization_code',
client_id: client_id,
client_secret: client_secret,
redirect_uri: 'http://127.0.0.1:3000/authed',
code: req.query.code,
},
}, function (err, response, body) {
console.log(response);
});
});
/* Success! We'll do something with the token here... */
app.post('/authed', function(req, res) {
res.send('we did it!')
});

Related

NodeJS Spotify API Implicit Grant flow tokens is invalid

Using spotify-web-api-node I create AuthorizeURL which works fine, Its going to my callback, then I receive my token, and store it in cookie. But when Im trying to create some request with this token, I get error:
"{"error":{"status":401,"message":"Invalid access token"}}"
Am I missing something? Here is the following code:
// Create and redirect to AuthorizeURL
spotify.get("/spotify/login", async (req, res) => {
let scopes = ['user-read-currently-playing'],
responseType = 'token',
showDialog = true;
let localSpotifyApi = new SpotifyWebApi({
clientId: client_id,
redirectUri: redirect_uri
});
res.redirect(localSpotifyApi.createAuthorizeURL(
scopes,
showDialog,
responseType
));
});
// Handle the callback
spotify.get('/spotify/callback', async (req, res) => {
let access_token = req.query.code;
res.cookie('spotify_access_token', access_token, { maxAge: 3600000});
return res.redirect('/spotify/api/get-currently-playing');
});
// Trying to use stored access token somewhere later
spotify.get('/spotify/api/get-currently-playing', async (req, res) => {
let credentials = {
clientId: client_id,
clientSecret: client_secret,
accessToken: req.cookies.spotify_access_token
};
let localSpotifyApi = new SpotifyWebApi(credentials);
localSpotifyApi.getMyCurrentPlayingTrack()
.then(function(data) {
console.log(data);
return res.json(data);
}, function(err) {
console.log(err.message);
return res.json({'err' : err});
});
});

Get oauth2.0 Access token with NodeJS

I'm having trouble getting an access token using node.
I can do it with Postman, but it expires when I restart my computer.
This is the code I made, reading some confusing documentation on the internet.
const axios = require('axios');
const config = {
url: 'https://aurl.com/connect/token',
method: 'get',
client_id: 'CLIENT_ID',
client_secret: 'CLIENT_SECRET',
username: 'email#mail.com.br',
password: '1234',
scope: 'scope',
grant_type: 'password_credentials'
};
axios(config)
.then(function (response) {
console.log(response.data);
})
.catch(function (error) {
console.log(error);
});
I'm not an experienced programmer, I need help. ://

Google People API: Request had invalid authentication credentials - Authorization Bearer header not setting up

I am trying to implement this Google People api for a chat application that I am working on. people API docs has only this example - https://github.com/googleworkspace/node-samples/blob/master/people/quickstart/index.js
I made some changes to use integrate it with my project.
// imports
const app = express();
app.use(cookieParser());
const SCOPES = ['https://www.googleapis.com/auth/contacts.readonly'];
const people = google.people('v1');
let credentials, oAuth2Client;
fs.readFile('./client_secret.json', async (err, content) => {
if (err) return console.error(err);
credentials = JSON.parse(content);
const { client_secret, client_id, redirect_uris } = credentials.web;
oAuth2Client = new google.auth.OAuth2(
client_id, client_secret, redirect_uris[1]);
});
app.get("/auth/google", (req, res) => {
console.log(req.cookies);
res.cookie("sample_cookie", "sample_value");
const authUrl = oAuth2Client.generateAuthUrl({
access_type: 'offline',
scope: SCOPES,
});
console.log('Authorize this app by visiting this url:', authUrl);
res.redirect(authUrl);
});
app.get("/contacts", (req, resp) => {
if (req.cookies.access_token && req.cookies.refresh_token) {
const token = {
access_token: req.cookies.access_token,
refresh_token: req.cookies.refresh_token,
scope: req.cookies.scope,
token_type: "Bearer",
expiry_date: req.cookies.expiry_date,
}
oAuth2Client.setCredentials(token);
const service = google.people({ version: 'v1', oAuth2Client });
service.people.connections.list({
resourceName: 'people/me',
pageSize: 10,
personFields: 'names,emailAddresses,phoneNumbers',
}, (err, res) => {
if (err) return resp.send(err);
const connections = res.data.connections;
if (connections) {
connections.forEach((person) => {
if (person.names && person.names.length > 0) {
resp.write(person.names);
} else {
resp.write('No display name found for connection.');
}
});
} else {
resp.write('No connections found.');
}
resp.end();
});
} else {
res.send("Something's wrong yet.")
}
})
app.get(["/auth/google/callback", "authorized"], async (req, res) => {
const code = req.query.code;
oAuth2Client.getToken(code, (err, token) => {
if (err) return console.error('Error retrieving access token', err);
oAuth2Client.setCredentials(token);
res.cookie("access_token", token.access_token);
res.cookie("refresh_token", token.refresh_token);
res.cookie("scope", token.scope);
res.cookie("token_type", token.token_type);
res.cookie("expiry_date", token.expiry_date);
res.send("Done.")
})
})
app.listen(3000, () => {
console.log("running");
})
but I am getting 401: unauthorized. All the changes that I made to the former (Google) example is just that instead of saving details to token, I am saving them as cookies, and I added routes to access it from browser. The example provided by Google works as expected. The changes I made works as well till the authorization point but when trying to access contacts route it returns the following response.
this is the response I am geeting (only included details that I believe to be necessary):
{
"response": {
"config": {
"oAuth2Client": {
"credentials": {
"access_token": "my_access_token",
"refresh_token": "my_refresh_token",
"scope": "https://www.googleapis.com/auth/contacts.readonly",
"token_type": "Bearer",
"expiry_date": 1609256514576
},
"redirectUri": "http://localhost:3000/auth/google/callback",
},
"url": "https://people.googleapis.com/v1/people/me/connections?pageSize=10&personFields=names%2CemailAddresses%2CphoneNumbers",
"method": "GET",
"headers": {
"Accept-Encoding": "gzip",
"User-Agent": "google-api-nodejs-client/0.7.2 (gzip)",
"Accept": "application/json"
},
},
"data": {
"error": {
"code": 401,
"message": "Request is missing required authentication credential. Expected OAuth 2 access token, login cookie or other valid authentication credential. See https://developers.google.com/identity/sign-in/web/devconsole-project.",
"errors": [{
"message": "Login Required.",
"domain": "global",
"reason": "required",
"location": "Authorization",
"locationType": "header"
}],
"status": "UNAUTHENTICATED"
}
},...
I tried to debug the code. I can't catch anything. But one thing I noticed is that in the above response I do not have Authorization header set. In successful API request from the google docs example, I receive
{
"config": {
"url": "https://people.googleapis.com/v1/people/me/connections?pageSize=10&personFields=names%2CemailAddresses%2CphoneNumbers",
"method": "GET",
"headers": {
"Accept-Encoding": "gzip",
"User-Agent": "google-api-nodejs-client/0.7.2 (gzip)",
"Authorization": "Bearer access-code",
"Accept": "application/json"
},
},
"data": {
"connections": [{...
I Don't get why my code isn't setting the Authorization header, also OAuthClient and credentials field is not present in this successful response. If instead of people api I try something like below in contacts route or make a GET request with Bearer token in postman I get the response correctly.
let bearer = `Bearer ${req.cookies.access_token}`;
request({
url: 'https://people.googleapis.com/v1/people/me/connections?pageSize=10&personFields=names%2CemailAddresses%2CphoneNumbers',
headers: {
'Authorization': bearer
}}, (err, res) => {
if (err) {
console.error(err);
} else {
resp.send(res);
}
}
);
I recieve the response correctly. But I do not want to do it this way. I can't figure out what's wrong with my code or if someone can provide any other working example... I also tried using passport.js and I get the same 401 unauthorized error.
// passport-setup.js
const passport = require('passport');
const GoogleStrategy = require('passport-google-oauth20').Strategy;
passport.serializeUser(function (user, done) {
done(null, user);
});
passport.deserializeUser(function (user, done) {
done(null, user);
});
passport.use(new GoogleStrategy({
clientID: "client-id",
clientSecret: "client-secret",
callbackURL: "http://localhost:3000/auth/google/callback",
passReqToCallback: true
},
function (req, accessToken, refreshToken, otherTokenDetails, profile, done) {
req.session.accessToken = accessToken;
req.session.refreshToken = refreshToken;
req.session.scope = otherTokenDetails.scope;
req.session.token_type = otherTokenDetails.token_type;
req.session.expiry_date = new Date().getTime() + otherTokenDetails.expires_in;
return done(null, profile);
}
));
index.js
// importing express, cors, bodyParser, passport, cookieSession, passport setup and googleapis
const app = express();
const people = google.people('v1');
// app.use(cors, bodyParser, cookieSession, passport init and session)
const isLoggedIn = (req, res, next) => {
if (req.user) {
next();
}
else {
res.sendStatus(401);
}
}
app.get("/success", isLoggedIn, (req, resp) => {
const oAuth2Client = new google.auth.OAuth2(id, secret and url...)
const token = {
access_token: req.session.accessToken,
refresh_token: req.session.refreshToken,
scope: req.session.scope,
token_type: req.session.token_type,
expiry_date: req.session.expiry_date,
}
oAuth2Client.setCredentials(token);
const service = google.people({ version: 'v1', oAuth2Client });
service.people.connections.list({
resourceName: 'people/me',
pageSize: 10,
personFields: 'names,emailAddresses,phoneNumbers',
}, (err, res) => {
if (err) return resp.send(err);
const connections = res.data.connections;
if (connections) {
console.log('Connections:');
connections.forEach((person) => {
if (person.names && person.names.length > 0) {
resp.write(person.names);
} else {
resp.write('No display name found for connection.');
}
});
} else {
resp.write("No connections.");
}
res.end();
});
})
app.get('/auth/google',
passport.authenticate('google', {
scope: ['profile', 'email', 'https://www.googleapis.com/auth/contacts'],
accessType: 'offline',
prompt: 'consent',
}));
app.get('/auth/google/callback',
passport.authenticate('google', { failureRedirect: '/login' }),
function (req, res) {
res.redirect('/success');
});
app.listen(3000, () => console.log("Server is up and running on port 3000."));
I have checked almost every similar Stack Overflow answer and GitHub issues. Nothing seems to work out.
The name for the second parameter that is passed into google.people is auth.
In JavaScript you can write {auth: auth} as simply {auth}. In the example provided by google, the name of the variable is same as the field name, That's why it is directly supplied as auth
const service = google.people({ version: 'v1', auth });
But your variable name is different than the field name. so change the name or just replace this one line with
const service = google.people({ version: 'v1', auth: oAuth2Client });
It is expecting auth as the second property but it receives a property wth name
oAuth2Client that's why it is not working.

23andMe API error: 'No grant_type provided.' 'invalid_request' OAuth2

I'm authenticating with the 23andMe API with OAuth 2. I'm able to receive the code after the user grants access. I'm currently trying to send a post request to receive the access token. I continue to receive this error:
data:
{ error_description: 'No grant_type provided.',
error: 'invalid_request' } }
I am using the axios package to make my post request. There is an error in my request because I got a successful 200 response and access token when I cuRL:
curl https://api.23andme.com/token/
-d client_id='zzz' \
-d client_secret='zzz' \
-d grant_type='authorization_code' \
-d code=zzz \
-d "redirect_uri=http://localhost:3000/receive_code/"
-d "scope=basic%20rszzz"
I'm able to receive the authorization code from the 23andMe server. I'm then redirected to my application. Here's my GET route:
router.get('/receive_code', function(req, res) {
axios.post('https://api.23andme.com/token/', {
client_id: zzz,
client_secret: zzz,
grant_type: 'authorization_code',
code: req.query.code,
redirect_uri: 'http://localhost:3000/receive_code/',
scope: "basic%20rs3094315"
}).then(function (response) {
console.log(response);
}).catch(function (error) {
console.log(error);
});
});
Any thoughts?
The problem is the form key you've placed in your payload. It should work like this:
axios.post('https://api.23andme.com/token/', {
client_id: zzz,
client_secret: zzz,
grant_type: 'authorization_code',
code: req.query.code
redirect_uri: 'http://localhost:3000/receive_code/',
scope: "basic%20rs3094315"
}).then(function (response) {
console.log(response);
}).catch(function (error) {
console.log(error);
});
I was able to solve the problem by using the simple-oauth2 npm package.
It can be found here: https://www.npmjs.com/package/simple-oauth2#express-and-github-example
// **********23ANDME OAUTH2************
var oauth2 = require('simple-oauth2')({
clientID: 'zzz',
clientSecret: 'zzz',
site: 'https://api.23andme.com',
tokenPath: '/token',
authorizationPath: '/authorize'
});
var authorization_uri = oauth2.authCode.authorizeURL({
redirect_uri: 'http://localhost:3000/receive_code/',
scope: 'basic analyses rs1234567',
state: 'jenvuece2a'
});
// *************************************
// In you view, place "/auth" in your <a> e.g. Click Me
router.get('/auth', function (req, res) {
res.redirect(authorization_uri);
});
router.get('/receive_code', function(req, res) {
var code = req.query.code;
if (!code) {
res.send('Error!!')
} else {
console.log('running');
oauth2.authCode.getToken({
code: code,
redirect_uri: 'http://localhost:3000/receive_code/'
}, saveToken);
function saveToken(error, result) {
if (error) {
console.log('Access Token Error', error.message);
} else {
token = oauth2.accessToken.create(result);
console.log(token);
}
};
res.render('/genetic_report', {layout: 'dash'});
}
});

Make a post request with ExpressJS

I need to make a POST request on my ExpressJS app... But i want to get the body results outside of the callback function in order to work with them...
I think I need a sync function...
var TOKEN;
exports.getToken = function(req, res){
var postData = {
client_id: CLIENT_ID,
client_secret: CLIENT_SECRET,
grant_type: 'authorization_code',
redirect_uri: REDIRECT_URI,
code: CODE
}
var url = 'https://api.instagram.com/oauth/access_token';
// Make my sync POST here and get the TOKEN
// example :
// request.post({....}, function(err, res, body){ TOKEN = body; });
// console.log(TOKEN);
res.render('index', {title: '****'});
}
Look at the Async library. The series or waterfall functions are what you want.
https://github.com/caolan/async#waterfall
Something along these lines:
async.waterfall([
function (callback) {
var postData = {
client_id: CLIENT_ID,
client_secret: CLIENT_SECRET,
grant_type: 'authorization_code',
redirect_uri: REDIRECT_URI,
code: CODE
}
var url = 'https://api.instagram.com/oauth/access_token';
//pass the body into the callback, which will be passed to the next function
request.post(postData, function (err, res, body) { callback(null,body); });
},
function (token, callback) {
//do something with token
console.log(token);
callback(null);
}
], function (err, result) {
// result now equals 'done'
res.render('index', { title: '****' });
});

Resources