I am using passport.js + passport-facebook-token to secure my API build with Strongloop's Loopback Framework.
Why is passport serializing the deserialized user again after it has successfully been deserialized? Also the passport.authenticate method is called on every request! What am I doing wrong?
Here is node's log:
deserializeUser, id: XXXXXXXXXXXXXXXX
User found.
serializeUser, id: XXXXXXXXXXXXXXXX
GET /api/events?access_token=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX 304 182ms
Here is the js code:
passport.use(new FacebookTokenStrategy({
clientID: XXXXXXXXXXXXXXXX,
clientSecret: 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX'
},
function(accessToken, refreshToken, profile, done) {
//check user table for anyone with a facebook ID of profile.id
User.findOne({
'facebookId': profile.id
}, function(err, user) {
if (err) {
return done(err);
}
if (user) {
console.log("User found.");
return done(err, user);
} else {
console.log("User not found.");
User.create({
email: profile.emails[0].value,
facebookId: profile.id,
password: 'secret'
}, function(err, user) {
console.log(user.id);
console.log(user.email);
console.log(user.facebookId);
console.log("User created");
return done(err, user);
});
}
});
}));
passport.serializeUser(function(user, done) {
console.log('serializeUser, id: ' + user.facebookId);
done(null, user.facebookId);
});
passport.deserializeUser(function(id, done) {
console.log('deserializeUser, id: ' + id);
User.findOne({
'facebookId': id
}, function(err, user) {
if (!err) {
done(null, user);
} else {
done(err, user);
}
});
});
Regarding your question about why passport.authenticate is called on every request, it is because you defined it as a middleware, probably before any routing logic happens.
If you have private and public sections on your app, you could do something like that :
// Define a specific that will handle authentication logic
app.get("/auth", passport.authenticate('facebook-token',function(){...});
// Public sections which do not require authentication
app.get("/public1",...);
app.post("/public2",...);
// Private sections which do require authentication
app.get("/private1", function(req,res,next){
if (req.isAuthenticated()){ // Check if user is authenticated
// do things...
}else{ // Wow, this guy is not authenticated. Kick him out of here !
res.redirect("/auth");
}
});
Now, if you have multiple private sections, you'll probably find it a bit tidious to do the same thing for each private section.
You could define a custom function that will check if the user is authenticated, and allow the request to proceed if he is.
Something like
function isThisGuyAuthenticated(req,res,next){
if (req.isAuthenticated()){
return next(); // Ok this guy is clean, please go on !
}else{
res.redirect("/auth"); // This guy is shady, please authenticate !
}
}
And use it like :
app.get("/private1",isThisGuyAuthenticated, doCrazySecretStuff); // doCrazySecretStuff will not be called if the user is not authenticated
app.get("/private2", isThisGuyAuthenticated, getCocaColaRecipe);
app.get("/private3", isThisGuyAuthenticated, flyToMars);
app.get("/public", showInfo); // showInfo will be called whether the user is authenticated or not
Now, if your app only has private sections, you could avoid repeating calls to isThisGuyAuthenticated by defining it as middleware (but not by defining passport.authenticate itself as a middleware !);
// Endpoint that will be hit is the user is redirected to /auth
// BEWARE it needs to be above the middleware, otherwise you'll end up with an infinite redirection loop
app.get("/auth", passport.authenticate('facebook-token',function(){...});
// Middleware that will be called on every request
app.use(isThisGuyAuthenticated);
// You app's endpoints
app.get("/private1", doCrazySecretStuff); // doCrazySecretStuff will not be called if the user is not authenticated
app.get("/private2", getCocaColaRecipe);
app.get("/private3", flyToMars);
Is that clear ?
EDIT : I mistakenly put the middleware before the "/auth" endpoint. Make sure it's placed after
Related
I'm having a big issue that I'm not sure is just a code design issue or I'm trying to do something that makes no sense.
I've implemented a local sign up using passport for authentication and JWT for route access and permissions. I send the token to the front end and save it to LocalStorage using AngularJS.
Everything there works and makes sense. But with twitter, I can't figure out how to implement the same strategy for getting the user logged in.
I've set up everything on the Twitter side, use passport for login and registering. But I don't see how it is possible to send the token to the front end because I can only use a GET request to receive the information from the Twitter API.
I redirect to Twitter login, redirect to the callback URL. But what comes after that? I have some relevant code that just returns the JWT. What I'm asking is how can I pass that to Angular in the best way possible?
Routes:
app.get('/login/twitter', passport.authenticate('twitter'));
app.get('/login/twitter/callback', function(req, res) {
passport.authenticate('twitter' , function(err, user, info) {
if(err) {
res.json({
'message': err
});
}
var token;
token = user.generateJwt();
res.status(200);
res.json({
"token" : token
});
})(req, res);
});
Passport:
passport.use(new TwitterStrategy({consumerKey: auth.twitterAuth.consumerKey, consumerSecret: auth.twitterAuth.consumerSecret, callbackURL: auth.twitterAuth.callbackURL}, function(req, key, keySecret, profile, done) {
User.findOne({'twitter.id' : profile.id}, function(err, user) {
if(err) {
return done(err);
}
if(user) {
if(!user.twitter.token) {
user.twitter.token = key;
user.twitter.username = profile.username;
user.twitter.displayName = profile.displayName;
user.save(function(err) {
if(err) {
res.json({
'message': err
});
}
return done(null, user);
});
}
return done(null, user);
}
let newUser = new User();
newUser.twitter.id = profile.id;
newUser.twitter.token = key;
newUser.twitter.username = profile.username;
newUser.twitter.displayName = profile.displayName;
newUser.twitter.registerDate = Date.now();
newUser.save(function(err) {
if(err) {
res.json({
'message': err
});
}
return done(null, newUser);
});
});
}));
The best way to do this from my experience is to pass the information you want to store in the JWT to the url and store the token in localStorage on the callback page. Once the user identifier is stored in the browser, redirect to the desired page.
My passport.js configuration goes like so:
const Local = require("passport-local").Strategy;
const USMODEL = require("../models/user.js");
passport.serializeUser(function(user, done) {
console.log("SERIALIZING USER");
done(null, user.id);
});
passport.deserializeUser(function(id, done) {
console.log("DESUSER", id);
var US = mongoose.model("RegUser", USMODEL);
US.findById(id, function(err, user) {
done(err, id);
});
});
passport.use("local-login", new Local({
usernameField: "email",
passwordField: "password",
passReqToCallback: true
},function(req, email, password, done) {
var US = mongoose.model("RegUser", USMODEL);
US.findOne({"email": email}, function(err, user){
if(err) throw err;
if(!user) return done(null, false);
if(!user.validPassword(password)) {
console.log("password not valid");
return done(null, false);
}
return done(null, user);
});
}));
I'm changing the mongoose model within each function because I juggle with multiple collections at a time and I like to have complete control of what's going on.
My router.js file has the following paths that make use of the passport middleware:
app.get("/user/login", function(req, res) {
res.render("signin");
});
app.post('/user/login', function (req, res){
passport.authenticate('local-login', function(err, user, info){
if (err) return res.redirect("/");
if (!user) return res.redirect('/');
else {
req.login(user, function(err) {
if (err) return next(err);
console.log("Request Login supossedly successful.");
return res.redirect('/admin/filter');
});
}
})(req, res);
});
Which, upon successful authentication, redirects to /admin/filter in the same router that goes like so.
app.get("/admin/filter", isLoggedIn, function(req, res){
//rendering stuff here
});
Now, the admin/filter request goes past a middleware called isLoggedIn which, in theory protects my endpoints. It goes like so:
function isLoggedIn(req, res, next) {
console.log("This is the authentication middleware, is req authenticated?");
console.log(req.isAuthenticated());
console.log("Does req.user exist?")
console.log(req.user);
return next();
}
Now, you would expect that because I called req.login and I got redirected to my endpoint of choice, the request would be authenticated. This is not the case.
Request Login supossedly successful.
This is the authentication middleware, is req authenticated?
false
Does req.user exist?
undefined
I can't seem to find the source of my problem. Everything checks out, as the strategy is being invoked, as well as the callback function and req.login which would render, in theory, a req.user object with data in it. One odd thing I've observed is that I don't see the passport.deserializeUser() method in action. Ever. But that could be tangential to the problem. Passport is definitely using my strategy and rendering a user object, but somehow this same object is not going into the request. Do you have any suggestion or idea about what is going on?
I solved the problem by juggling around with the tutorial I started with when I first learned how to use the Passport middleware. Turns out I was doing the configuring wrong: My code used to be like this in the server file:
pass = require("passport");
app.use(pass.initialize());
app.use(pass.session());
require("./app/config/passport.js")(pass);
when it should have been this:
pass = require("passport");
require("./app/config/passport.js")(pass);
app.use(pass.initialize());
app.use(pass.session());
Either I missed the part in the documentation where it's specified that configuration must come before initialization or it's simply written off as a trivial thing to remark. Either way, I solved my problem.
Make sure withCredentials: true while sending the post request.
// register
axios.post(uri, {
email: email,
password: password,
confirmPassword: confirmPassword
}, {
withCredentials: true
})
I'm attempting to authenticate a user using Box.com OAuth2.0. I make the initial call and login which redirects to my callback url with the authorization code. At this point my server handles the callback using passport but for some reason it returns a 302 and redirects to the beginning of the oauth authentication process.
//box authentication routes
app.get('/api/box', passport.authorize('box'));
// the callback after box has authorized the user
app.get('/api/box/callback', passport.authorize('box', {
successRedirect: '/',
failureRedirect: '/login'
})
);
I verified that my route is being called by using my own handler and the request data seems to be correct. Box returns a 200 and the url contains the authorization code.
app.get('/api/box/callback', function(req, res) {
console.log('auth called')
});
This is my passport strategy:
passport.use(new BoxStrategy({
clientID: config.box.clientID,
clientSecret: config.box.clientSecret,
callbackURL: config.box.callbackURL,
passReqToCallback: true
},
function(req, accessToken, refreshToken, profile, done) {
process.nextTick(function() {
if(!req.user) {
// try to find the user based on their google id
User.findOne({ 'box.id' : profile.id }, function(err, user) {
if (err)
return done(err);
if (user) {
// if a user is found, log them in
return done(null, user);
} else {
// if the user isnt in our database, create a new user
var newUser = new User();
// set all of the relevant information
newUser.box.id = profile.id;
newUser.box.accessToken = accessToken;
newUser.box.refreshToken = refreshToken;
newUser.box.name = profile.name;
newUser.box.email = profile.login;
// save the user
newUser.save(function(err) {
if (err)
throw err;
return done(null, newUser);
});
}
});
} else {
// user already exists and is logged in, we have to link accounts
var user = req.user;
// update the current users box credentials
user.box.id = profile.id;
user.box.accessToken = accessToken;
user.box.refreshToken = refreshToken;
user.box.name = profile.name;
user.box.email = profile.login;
// save the user
user.save(function(err) {
if (err)
throw err;
return done(null, user);
});
}
});
}
));
Would appreciate any insight as to what might be causing this redirect behavior.
It ended up being an instance of PeerServer that was somehow causing the redirect.
Couple of questions. I set up authentication in my app using the following code:
passport.use(new LocalStrategy(function(username, password, done){
Users.findOne({ username : username},function(err,user){
if(err) { return done(err); }
if(!user){
return done(null, false, { message: 'Incorrect username' });
}
hash( password, user.salt, function (err, hash) {
if (err) { return done(err); }
if (hash == user.hash) return done(null, user);
done(null, false, { message: 'Incorrect password' });
});
});
}));
app.get('/admin', function (req, res){
res.render('login.jade');
});
app.post('/admin', function (req, res){
passport.authenticate('local', { successRedirect: '/main',
failureRedirect: '/login',
failureFlash: true });
});
Users is a DB schema that includes username, password, and hash.
The first and most obvious question is, how do I add a new user to the database? I specifically don't want a sign-up page but want to manually add every new user. Is there a way to do this manually?
Next, how do I modify my existing routes to only work if the user is authenticated? For example, I have:
app.get('/comment/:commentID', admin.renderComment);
The above renderCommit is a large handler function but accessing this page should only work if the user is authenticated. How do I check that?
Creating a new user should be relatively straightforward, from a standalone script for instance:
// Load your models, set up database connection, etc.
...
// Create a new user:
Users.create({
username : USERNAME,
password : HASHED_PASSWORD,
salt : GENERATED_SALT,
}, function(err, user) {
if (err) {
console.error('Error creating user:', err.message);
} else {
console.log('User created successfully');
}
});
As for making sure that a user is logged in when certain routes are requested, you can use a simple middleware for that:
app.get('/comment/:commentID', ensureAuthenticated, admin.renderComment);
I'm trying to get some basic Google authentication going with PassportJS.
The amount of information on the web seems rather limited and I keep hitting issues so thought I would try here.
I am using the code from https://github.com/mattgaidica/twitter-mongo with a few modifications to use it for Google OAuth (it doesn't use the Twitter keys, it uses passport-google, passport.authenticate('google', ...)).
I keep ending up with this error: 'Error: failed to serialize user into session'
passport.serializeUser(function(user, done) {
console.log(user); //does not have the fields we created earlier, user.uid does not exist.
done(null, user.uid);
});
My Passport Strategy:
passport.use(new GoogleStrategy({
returnURL: "http://localhost:3000/auth/google/callback"
},
function(identifier, profile, done) {
User.findOne({uid: profile.id}, function(err, user) {
if(user) {
done(null, user);
} else {
var user = new User();
user.provider = "google";
user.uid = identifier;
user.name = profile.displayName;
user.save(function(err) {
if(err) { throw err; }
done(null, user);
});
}
})
}
));
The fields of this other user are not the same as the one originally created, what happened to the other user?
I'm a bit lost as to what is going on here, if anyone could let me know what I'm doing wrong I would be very grateful.
In your query, you use profile.id:
User.findOne({uid: profile.id}, function(err, user) { ... });
But that should be identifier.
Also, you're using the OpenID-version of the Passport Google plug-in, which is rather old (and doesn't even work properly on Node 0.10). I'd suggest you use passport-google-oauth instead.