Return accesstoken after Oauth Authentication in passport.js - passport.js

I want the following flow in my app
User selects authenticate with google
I redirect to google and get the callback (This happens using Passport)
I now want to return an auth token that is specific to my application in the form of response to user.
var GoogleStrategy = require('passport-google-oauth').OAuth2Strategy;
passport.use(new GoogleStrategy({
clientID: GOOGLE_CLIENT_ID,
clientSecret: GOOGLE_CLIENT_SECRET,
callbackURL: "http://127.0.0.1:3000/auth/google/callback"
},
function(accessToken, refreshToken, profile, done) {
// RETURN ACCESS TOKEN IN THE RESPONSE
}
));
I do get the success callback, but I don know how to return a custom access token/response from the callback function.
I guess the answer related to using of done(), or serialization or deserialization which I am not clear from the docs or any examples. Can you please also explain how done(), or serialization or deserialization are used.

You can do this in serialization and deserialization. See this passport example
userSchema.methods.generateRandomToken = function () {
var user = this,
chars = "_!abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890",
token = new Date().getTime() + '_';
for ( var x = 0; x < 16; x++ ) {
var i = Math.floor( Math.random() * 62 );
token += chars.charAt( i );
}
return token;
};
var User = mongoose.model('User', userSchema);
//serialize
passport.serializeUser(function (user, done){
var createAccessToken = function() {
var token = user.generateRandomToken()
User.findOne({
accessToken: token
}, function(err, existingUser){
if (err) { return done(err) }
if (existingUser) {
createAccessToken()
} else {
user.set('accessToken', token)
user.save(function(err){
if (err) { return done(err) }
return done(null, user.get('accessToken'))
})
}
})
}
if (user._id) {
createAccessToken()
}
})
//deserialize
passport.deserializeUser(function(token, done){
User.findOne({
accessToken : token
}, function(err, user){
done(err, user)
})
})

Related

Passport.js GoogleStrategy not working! Getting error "Cannot read properties of undefined (reading '0')"

I am trying to implement passport.js google login, but as you can see in my code below I am having an issue with the profile object which is not behaving like the documentation says it should. Please help.
Also, I do not wish to you passports build in session support! I have my own functions for creating sessions and authenticating if the user is logged in.
const passport = require('passport');
const GoogleStrategy = require('passport-google-oidc');
const User = require('../models/user-schema');
async function create_user(name, email) {
// too long to show, just assume it works (not relevant for my question anyways)
const user = await User.create({});
return login_user(user)
}
function login_user(user) {
req.session.user_id = user._id;
delete user._doc.hash;
delete user._doc.salt;
return user;
}
passport.use(new GoogleStrategy({
clientID: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SEC,
callbackURL: 'http://localhost:4000/auth/login-google/redirect/',
scope: [ 'email', 'profile' ]
},
async function(request, accessToken, refreshToken, profile, done) {
try {
console.log(profile); // Gives a long string for some reason
console.log(profile.id); // Gives undefined even though it is used in the documentation
const email = profile.emails[0].value; // Here it throws the error even though this is what I have seen others use
// (!)
// Everything beyond this point I havent had the chance to test yet because the profile object, well I guess, ISNT AN OBJECT!
// (!)
const existing_user = await User.findOne({ email: email });
const user = (existing_user) ? login_user(existing_user) : await create_user(profile.displayName, email);
return done(null, user);
}
catch (err) {
console.log('errror: ' + err.message);
return done(err);
}
}));
router.get('/login-google', passport.authenticate('google'));
router.get('/login-google/redirect/', passport.authenticate('google', {
successRedirect: '/login-google/success',
failureRedirect: '/login-google/failure'
}));
router.get('/login-google/success', (req, res) => {
console.log('success');
});
router.get('/login-google/failure', (req, res) => {
console.log('failure');
});
You're importing GoogleStrategy from passport-google-oidc which has a different signature. The current signature of your implementation belongs to GoogleStrategy from passport-google-oauth2.
According to the passport's documentation for passport-google-oidc, your function's signature should be something like this:
var GoogleStrategy = require('passport-google-oidc');
passport.use(new GoogleStrategy({
clientID: process.env['GOOGLE_CLIENT_ID'],
clientSecret: process.env['GOOGLE_CLIENT_SECRET'],
callbackURL: 'https://www.example.com/oauth2/redirect'
},
function verify(issuer, profile, cb) {
db.get('SELECT * FROM federated_credentials WHERE provider = ? AND subject = ?', [
issuer,
profile.id
], function(err, cred) {
if (err) { return cb(err); }
if (!cred) {
// The account at Google has not logged in to this app before. Create a
// new user record and associate it with the Google account.
db.run('INSERT INTO users (name) VALUES (?)', [
profile.displayName
], function(err) {
if (err) { return cb(err); }
var id = this.lastID;
db.run('INSERT INTO federated_credentials (user_id, provider, subject) VALUES (?, ?, ?)', [
id,
issuer,
profile.id
], function(err) {
if (err) { return cb(err); }
var user = {
id: id.toString(),
name: profile.displayName
};
return cb(null, user);
});
});
} else {
// The account at Google has previously logged in to the app. Get the
// user record associated with the Google account and log the user in.
db.get('SELECT * FROM users WHERE id = ?', [ cred.user_id ], function(err, user) {
if (err) { return cb(err); }
if (!user) { return cb(null, false); }
return cb(null, user);
});
}
}
})
));

Passport facebook doesn't create new user and times out

I'm using passport with multiple strategies (JWT, Google and Facebook). All work fine except Facebook, when an existing user tries to login. In this case it just times out and I get a 504.
It tries to call https://api.example.com/users/facebook/callback/?code=... before I get the timeout error.
I tried the exact same logic from my Google strategy, where everything works fine, but it doesn't help. I tried different online tutorials but none of them worked either.
So what am I doing wrong?
Passport.js config - Facebook code block
function(passport) {
passport.serializeUser((user, cb) => {
cb(null, user);
});
passport.deserializeUser((user, cb) => {
cb(null, user);
});
passport.use(new FacebookStrategy({
proxy: true,
clientID: keys.facebook.clientID,
clientSecret: keys.facebook.clientSecret,
callbackURL: "https://api.example.com/users/facebook/callback",
profileFields: ['id', 'displayName', 'email']
},
async (accessToken, refreshToken, profile, done) => {
const { email, first_name } = profile._json;
try {
const oldUser = await User.findOne({ email: email });
if (oldUser) {
return done(null, oldUser);
}
} catch (err) {
console.log(err);
return done(null, false);
}
// register user
try {
const newUser = await new User({
facebook: true,
email: email,
name: first_name,
verified: true,
}).save();
done(null, newUser);
} catch (err) {
console.log(err);
return done(null, false);
}
}
))
}
User auth route
// FACEBOOK
router.get("/facebook", passport.authenticate("facebook"));
router.get("/facebook/callback", generalTooManyRequests, passport.authenticate("facebook"), (req, res) => {
const referer = req.cookies["Origin"]
let redirectURL
// login did NOT work!
if (!req.user) {
redirectURL = "https://app.example.com/login/fehler-facebook"
if (referer === "website") {
redirectURL = "https://example.com/login/?fehler-facebook"
}
res.redirect(redirectURL)
}
// login did work!
else {
redirectURL = "https://app.example.com/callback/token="
if (referer === "website") {
redirectURL = "https://example.com/callback/?token="
}
const tokenObject = utils.issueJWT(req.user);
res.redirect(redirectURL + tokenObject.token)
}
});

Failed to serialize user into session with passportjs and cookie-session

I'm trying to get authentication with Google+ OAuth. To achieve this I'm using passportjs with Google strategy (passport-google-oauth20 module) but I'm stuck in an error while passport tries to serialize the user into a session (using cookie-session).
The error comes after login in the Google site.
The code:
passport.serializeUser((user, done) => {
console.log('serialize ' + (user.id == undefined ? null : user.id));
console.log(user);
return done(null, (user.id == undefined ? null : user.id));
});
passport.deserializeUser((id, done) => {
console.log('dserialize id ' + id);
db.connect((err, client, don) => {
if (err) throw err
client.query('SELECT * FROM "AppUsers" WHERE "googleId" = $1', [id], (err, res) => {
don();
if (err) {
console.log(err.stack);
} else {
console.log(res.rows[0]);
if (res.rows[0]) {return done(null, res.rows[0]);}
else {return done(null, null);}
}
});
});
});
Edit:
async function checkGoogle(profile) {
const client = await db.connect();
try {
const { rows } = await client.query('SELECT * FROM "AppUsers" WHERE "googleId" = $1', [profile.id]);
let currentUser = rows[0];
console.log(currentUser);
if (currentUser) {
console.log('in db ' + currentUser.id);
console.log(currentUser);
return currentUser;
} else {
const { rows } = await client.query('INSERT INTO "AppUsers" ("googleId") VALUES ($1) RETURNING *', [profile.id]);
let newUser = rows[0];
console.log('not in db ' + newUser.id);
console.log(newUser);
return newUser;
}
} catch (error) {
alert(error);
} finally {
client.release();
}
}
passport.use(
new GoogleStrategy({
// options for google strategy
clientID: keys.google.clientID,
clientSecret: keys.google.clientSecret,
callbackURL: '/auth/google/redirect'
}, (accessToken, refreshToken, profile, done) => {
// check if user already exists in our own db
return done(null, checkGoogle(profile));
})
);
Output:
The error screen
Please tell me if you need more information about.
You need to wait for the checkGoogle function to return data by using async/await.
passport.use(
new GoogleStrategy({
// options for google strategy
clientID: keys.google.clientID,
clientSecret: keys.google.clientSecret,
callbackURL: '/auth/google/redirect'
}, async (accessToken, refreshToken, profile, done) => {
const user = await checkGoogle(profile);
// check if user already exists in our own db
return done(null, user);
})
);

Protecting an API with Scopes (oauth2orize, passport, express, Nodejs)

I'm trying to create an API with node/express, and secure it with Passport and oauth2orize. I've got the API working, I've got the oauth2 stuff working, but I can't seem to figure out how to implement securing API methods with scopes.
The oauth2orize token hander-outer:
server.exchange(oauth2orize.exchange.password(function (client, username, password, scope, done) {
scope = scope || ['unauthorized'];
db.collection('oauth_users').findOne({username: username}, function (err, user) {
if (err) return done(err);
if (!user) return done(null, false);
for (i in scope)
if(user.scope.indexOf(scope[i]) < 0) return done(null, false);
bcrypt.compare(password, user.password, function (err, res) {
if (!res) return done(null, false);
var token = utils.uid(256)
var refreshToken = utils.uid(256)
var tokenHash = crypto.createHash('sha1').update(token).digest('hex')
var refreshTokenHash = crypto.createHash('sha1').update(refreshToken).digest('hex')
var expirationDate = new Date(new Date().getTime() + (3600 * 1000))
db.collection('oauth_access_tokens').save({token: tokenHash, expirationDate: expirationDate, clientId: client.clientId, userId: username, scope: scope}, function (err) {
if (err) return done(err)
db.collection('oauth_refresh_tokens').save({refreshToken: refreshTokenHash, clientId: client.clientId, userId: username}, function (err) {
if (err) return done(err)
done(null, token, refreshToken, {expires_in: expirationDate})
})
})
})
}) }))
The passport bearer token checker:
passport.use("accessToken", new BearerStrategy(
{passReqToCallback: true},
function (req, accessToken, done) {
console.dir(req.params);
var accessTokenHash = crypto.createHash('sha1').update(accessToken).digest('hex')
db.collection('oauth_access_tokens').findOne({token: accessTokenHash}, function (err, token) {
if (err) return done(err);
if (!token) return done(null, false);
if (new Date() > token.expirationDate) {
db.collection('oauth_access_tokens').remove({token: accessTokenHash}, function (err) { done(err) });
} else {
db.collection('oauth_users').findOne({username: token.userId}, function (err, user) {
if (err) return done(err);
if (!user) return done(null, false);
// no use of scopes for no
var info = { scope: '*' }
done(null, user, info);
})
}
})
}))
The API security:
router.get('/restricted', passport.authenticate('accessToken', { scope: "unauthorized", session: false }), function (req, res) {
res.send("Restricted Function");})
I can find no example of accessing the "scope" option passed in passport.authenticate to passport.use. I was thinking it was in the req object, but I can't find it in there. Any help?
A bit late. But thought this might help. The info object that you pass as the third parameter can be used from the middleware as req.authInfo. If you have scope attached with the user object or if you have declared it at passport.authenticate initialize level, you can pass it through this parameter and make use of in the middleware. Please have a look at this link Usage of scopes

Does compoundjs-passport always expects user to be saved to the database after authentication?

I am developing a simple user crud module using compoundjs, compound-passport and passport-facebook. It used the default approach to generate a crud and it works fine if I want to save it to the database but I want to save it to a session here and save the user at a later stage the only problem is I do not get a request or session in model.
Here is my user.js model
module.exports = function (compound, User) {
// define User here
User.findOrCreate = function (data, done) {
console.log(data.profile.emails[0].value);
/* FACEBOOK OPENID */
if (data.profile.provider == "facebook") {
User.all({
where: {
email_id: data.profile.emails[0].value
}, limit: 1
}, function (err, user) {
if(data.profile.gender != '' && data.profile.gender != undefined)
var givenGender = (data.profile.gender == 'male')?'M':'F'
else
var givenGender = '';
if (user[0]) return done(err, user[0]);
var userDet = {
firstname: data.profile.name.givenName,
lastname: data.profile.name.familyName,
email_id: data.profile.emails[0].value,
gender:givenGender,
dob: new Date(data.profile._json.birthday),
registered_from:"facebook",
created_at:new Date(),
updated_at:new Date()
};
req.session.userdet = userDet;//This does not work
done();
//req.session.userdet = userDet;
// User.create({
// displayName: data.profile.displayName,
// email: data.profile.emails[0].value,
// facebookID: data.openId,
// provider:"facebook",
// createDate:new Date(),
// lastLogin:new Date()
// }, done);
});
} else
/* SOMETHING NOT KNOWN YET */
{
console.log("DONT NOW HOW TO HANDLE THIS USER.")
console.log(data.profile);
}
};
};
The req.session.userdet = userDet; above does not work as I don't have a request object in the model.
Here is my facebook.js which implements the facebook strategy
var passport = require('passport');
exports.callback = function(token, tokenSecret, profile, done) {
exports.User.findOrCreate({
facebookId: profile.id,
profile: profile
}, function (err, user) {
return done(err, user);
});
};
exports.init = function (conf, app) {
var Strategy = require('passport-facebook').Strategy;
passport.use(new Strategy({
clientID: conf.facebook.apiKey,
clientSecret: conf.facebook.secret,
callbackURL: conf.baseURL + 'users/signupnew'
}, exports.callback));
passport.serializeUser(function(user, done) {
console.log("I am here");
console.log(user);
done(null, user);
});
passport.deserializeUser(function(id, done) {
exports.User.findById(id, function(err, user) {
done(err, user);
});
});
// app.get('/auth/facebook',
// passport.authenticate('facebook', { scope: [ 'email,user_likes,user_birthday' ] }));
// app.get('/auth/facebook/callback',
// passport.authenticate('facebook', {
// failureRedirect: conf.failureRedirect || '/'
// }), exports.redirectOnSuccess);
app.get('/users/signupnew',passport.authenticate('facebook', {
failureRedirect: conf.failureRedirect || '/'
}),function(req,res){
req.session.redirect = '/users/signup';
exports.redirectOnSuccess(req,res);
});
};
Basically I want to implement a multi step sign up so I have collect all the data in the session and then register a user. I apologise if my understanding about the flow is faulty as I am a new user to node and compound.

Resources