My problem is that I'm getting the error "Error: Failed to serialize user into session". I'm confused, because I've set a serializeUser function, but it doesn't appear to be called (my console.log isn't being printed).
This is while I'm following the feathers passport tutorial: http://feathersjs.com/learn/authorization/
Note: my suspicion is that feathers-passport uses a different "passport" object than my own library. Unfortunately I have no idea how I would rememdy such an issue. It seems to me it's just horrendous design by Passport to not work by passing around instances, and instead attaching things to itsself directly.
I'm setting up passport for serialization and authentication using the following:
var LocalStrategy = require('passport-local').Strategy;
function GetPassport(userService, Passport) {
console.log('passport has been prepared.\n');
Passport.serializeUser(function(user, done) {
console.log('user: ', user);
done(null, user._id);
});
Passport.deserializeUser(function(id, done) {
userService.get(id, {}, done);
});
Passport.use(new LocalStrategy(function(username, password, done) {
userService.authenticate(username, password, done);
}));
return Passport;
}
module.exports = GetPassport;
Then I'm using:
var userService = UserService(config.db);
var passport = GetPassport(userService);
app.post('/login', passport.authenticate('local'));
If you need more details here is UserService:
var MongoDB = require('feathers-mongodb');
var Crypto = require('crypto');
var UserService = function(database) {
return MongoDB({
db: database,
collection: '_users',
}).extend({
authenticate: function(username, password, callback) {
this.find({query: {username: username}}, function(error, users) {
if(error)
callback(error);
var user = users[0];
if(!user)
return callback(new Error('No User Found'));
if(user.password !== hash(password, user.salt))
return callback(new Error('Password Is Incorrect'));
//success, return the authenticated user
return callback(null, user);
});
},
setup: function() {
this.before({
create: function(hook, next) {
//Create the salt
var salt = Crypto.randomBytes(128).toString('base64');
hook.data.salt = salt;
hook.data.password = hash(hook.data.password, hook.data.salt);
next();
},
});
},
});
};
module.exports = UserService;
function hash(string, salt) {
var shasum = Crypto.createHash('sha256');
shasum.update(string + salt);
return shasum.digest('hex');
}
The error trace:
Error: Failed to serialize user into session
at pass (/Users/funk/Development/Projects/generic_rest_server/node_modules/feathers-passport/node_modules/passport/lib/authenticator.js:277:19)
at Authenticator.serializeUser (/Users/funk/Development/Projects/generic_rest_server/node_modules/feathers-passport/node_modules/passport/lib/authenticator.js:295:5)
at IncomingMessage.req.login.req.logIn (/Users/funk/Development/Projects/generic_rest_server/node_modules/passport/lib/http/request.js:48:29)
at Strategy.strategy.success (/Users/funk/Development/Projects/generic_rest_server/node_modules/passport/lib/middleware/authenticate.js:228:13)
at verified (/Users/funk/Development/Projects/generic_rest_server/node_modules/passport-local/lib/strategy.js:83:10)
at /Users/funk/Development/Projects/generic_rest_server/user-service.js:22:24
at /Users/funk/Development/Projects/generic_rest_server/node_modules/feathers-mongodb/node_modules/mongoskin/node_modules/mongodb/lib/mongodb/cursor.js:158:16
at commandHandler (/Users/funk/Development/Projects/generic_rest_server/node_modules/feathers-mongodb/node_modules/mongoskin/node_modules/mongodb/lib/mongodb/cursor.js:651:16)
at /Users/funk/Development/Projects/generic_rest_server/node_modules/feathers-mongodb/node_modules/mongoskin/node_modules/mongodb/lib/mongodb/db.js:1670:9
at Server.Base._callHandler (/Users/funk/Development/Projects/generic_rest_server/node_modules/feathers-mongodb/node_modules/mongoskin/node_modules/mongodb/lib/mongodb/connection/base.js:382:41)
The answer was in my note.
This should be helpful for anyone else who gets stuck following:
"http://feathersjs.com/learn/authorization/"
You must provide the FeathersPassport call with the passport option. If not, feathers-passport well use a different version of passport, than the one you add serializeUser to:
app.configure(FeathersPassport(function(result) {
// MongoStore needs the session function
var MongoStore = ConnectMongo(result.createSession);
result.secret = 'noymysecret';
result.store = new MongoStore({
db: config.db,
});
result.resave = false;
result.saveUninitialized = false;
//*HERE*//
result.passport = passport;
//**//
return result;
}));
I blame Passport inexplicably being a singleton, for not noticing this sooner.
Related
I have been building this project from a tutorial. The signup functionality works fine but the login feature doesn't work. Whenever I try logging in a registered user using postman the error I get is
Error: Unknown authentication strategy "local"
In the other posts on stack overflow, I didn't find a solution to this error. Passport, passport-local and passport-jwt are all installed so that shouldn't be the issue. I would really appreciate any sort of help.
passport.js
require('dotenv').config();
const passport = require('passport');
const LocalStrategy = require('passport-local').Strategy;
const JWTStrategy = require('passport-jwt').Strategy;
const User = require('./models/User');
// Environment variables
const STRATEGY_KEY = process.env.STRATEGY_KEY;
const cookieExtractor = req => {
let token = null;
// Retrieve the token from cookies
if (req && req.cookies) {
token = req.cookies['access_token'];
}
return token;
};
const jwtOptions = {
jwtFromRequest: cookieExtractor,
secretOrKey: STRATEGY_KEY,
};
// Authorization for protected routes
passport.use(
new JWTStrategy(jwtOptions, (payload, done) => {
User.findById({ _id: payload.sub }, (err, user) => {
// Check for error
if (err) return done(err, false);
// Check if user exists
if (user) return done(null, user);
return done(null, false);
});
})
);
// Local strategy using username and password
passport.use(
new LocalStrategy((username, password, done) => {
User.findOne({ username }, (err, user) => {
// Error while fetching the user from database
if (err) return done(err);
// No such user exists
if (!user) return done(null, false);
// Check if entered password matches
user.comparePassword(password, done);
});
})
);
routes.js
require('dotenv').config();
const express = require('express');
const passport = require('passport');
const router = express.Router();
const STRATEGY_KEY = process.env.STRATEGY_KEY;
const signToken = userID => {
return jwt.sign(
{
iss: STRATEGY_KEY,
sub: userID,
},
STRATEGY_KEY,
{
expiresIn: '1h',
}
);
};
router.post(
'/signin',
passport.authenticate('local', { session: false }),
(req, res) => {
if (req.isAuthenticated()) {
const { _id, username, email } = req.user;
const token = signToken(_id);
res.cookie('access_token', token, {
httpOnly: true,
sameSite: true,
});
res.status(200).json({
isAuthenticated: true,
user: {
username,
email,
},
});
}
}
);
module.exports = router;
So after many hours of debugging, the solution I found to this problem was that I didn't import passport.js file in routes.js file, which I was not expecting since that import stays there ideal not doing anything, not being part of any code(exceot the import) but I was wrong. The passport configuration we make in that file is imported under the hood even though it doesn't take part in any further lines of that file.
I already checked multiple answers on Stackoverflow, and also went through on the documentation but I still cannot figure out my problem. when I try to sign in and signup it's working perfectly I have my token. It's nightmare to just fetch my current_user get('/isAuth') I get undefined !!!!
const Authentication = require("../controllers/authentication");
const passport = require("passport");
const requireAuth = passport.authenticate('jwt', {session: false});
const requireSignin = passport.authenticate('local', {session: false});
module.exports = app => {
app.post('/signup', Authentication.signup);
app.post('/signin', requireSignin, Authentication.signin);
// Current User is undefined !!!!!
app.get('/isAuth', Authentication.fetchUser);
my passport.js
const keys = require("../config/keys");
const passport = require("passport");
const User = require("../models/User");
const JwtStrategy = require("passport-jwt").Strategy;
const ExtractJwt = require('passport-jwt').ExtractJwt;
const localStrategy = require("passport-local");
// Create local strategy
const localOptions = { usernameField: "email" };
const localLogin = new localStrategy(localOptions, function(email,password,done) {
// verify this username and password, call done with the user
// if it is the correct username and password
// otherwise, call done with false
User.findOne({ email: email }, function(err, user) {
if (err) {return done(err);}
if (!user) {return done(null, false);}
// compare passwords - is password is equal to user.password?
user.comparePassword(password, function(err, isMatch) {
if (err) {return done(err);}
if (!isMatch) {return done(null, false);}
return done(null, user);
});
});
});
// setup option for jwt Strategy
const jwtOptions = {
jwtFromRequest: ExtractJwt.fromHeader('authorization'),
secretOrKey: keys.secret
};
// Create Jwt strategy
const jwtLogin = new JwtStrategy(jwtOptions, function(payload, done) {
// See if the user Id in the payload exists in our database
// If does, call 'done' with that other
// otherwise, call done without a user object
User.findById(payload.sub, function(err, user) {
if (err) {return done(err, false);}
if (user) {
done(null, user);
} else {
done(null, false);
}
});
});
// Tell passport to use this strategy
passport.use(jwtLogin);
passport.use(localLogin);
// Generate token
passport.serializeUser((user, done) => {
done(null, user.id);
});
passport.deserializeUser((id, done) => {
User.findById(id).then(user => {
done(null, user);
});
});
./controller/authentication.js
const User = require('../models/User');
const jwt = require('jwt-simple');
const config = require('../config/keys');
function tokenForUser(user){
const timestamp = new Date().getTime();
return jwt.encode({sub: user.id, iat: timestamp}, config.secret);
}
exports.signup = function(req,res,next){
console.log(req.body)
const email = req.body.email;
const password = req.body.password;
if(!email || !password){
return res.status(422).send({error: 'You must provide email and password'});
}
// See if user with the given email exists
User.findOne({email: email}, function(error, existingUser){
if (error){return next(error)};
// if a user with email does exist, return an error
if (existingUser){
return res.status(422).send({error: 'Email is in use'});
}
// if a user with email does not exist, create and save record
const user = new User({
email: email,
password: password
});
user.save(function(error){
if (error){return next(error);}
// respond to request indicating the user was created
res.json({token: tokenForUser(user)});
})
})
}
exports.signin = function (req,res,next){
// user has already had their email and password auth
// we just need to give them a token
res.send({token: tokenForUser(req.user)});
}
// here is my problem...
exports.fetchUser = function (req, res, next) {
console.log('this is ',req.user)
};
Still stuck for many days... it's a nightmare!!! if someone has the solution.
after sign in if I want to go to my route /isAuth to check my user data:
Have your tried using a middleware that calls the isAuthenticated function on req object? This function is added by passport and is generally the recommended way to check if a request is authenticated.
function isLoggedIn(req, res, next) {
if (req.isAuthenticated()) {
return next();
}
res.redirect("/");
}
Then you can call this function when a user hits your isAuth route:
app.get('/isAuth', isLoggedIn, Authentication.fetchUser);
Bcrypt is throwing an Incorrect arguments error which I traced back to this function in user.js
userSchema.methods.comparePassword = (candidatePassword, callback) => {
bcrypt.compare(candidatePassword, this, (err, isMatch) => {
console.log('candidatePassword= ', candidatePassword, '& this= ', this);
if (err) { return callback(err); }
callback(null, isMatch);
});
};
/*
candidatePassword= bird
this= {}
this.password= undefined */
The user object is coming back as an empty object, and therefore this.password is undefined. I assume the this parameter in bcrypt.compare refers to the userSchema instance. The userSchema is declared in passport.js
const passport = require('passport');
const ExtractJwt = require('passport-jwt').ExtractJwt;
const JwtStrategy = require('passport-jwt').Strategy;
const LocalStrategy = require('passport-local').Strategy;
const User = require('../models/user');
const config = require('../config');
var localOptions = {
usernameField: 'email',
};
// Verifies user by checking if a password matches the specified email during signin
var localStrategy = new LocalStrategy(localOptions, function (email, password, done) {
User.findOne({ email:email.toLowerCase()}, function (err, user) {
console.log('/passport.js/localStrategy- user object: ', user)
if (err) { return done(err); }
if (!user) { return done(null, false); }
user.comparePassword(password, function (err, isMatch) {
console.log('/passport.js/localStrategy- password: ', password)
if (err) { return done(err); }
if (!isMatch) { return done(err, false); }
return done(null, user);
});
});
});
// ... jwt strategy ...
passport.use(localStrategy);
/*
user object: { _id: 58a1018dc3f89eb5955b8638,
email: 'bird#bird.com',
password: '$2a$10$lAJ9hoGKt9ggfk1TISfkOedxDIs/waLB5e4PccHAKt286XCKCY0/q',
__v: 0 } */
I'm not sure quite what the issue as it seems a user object is returned with an encrypted password field from mongodb, and user.comparepassword() is called...
I signed the user up with the same Schema object as well.
Any help / tips appreciated!
You are only setting up your model so that it pulls in the candidatePassword but never finds the stored password from the database. Since this is returning an empty object, either the email is not being matched or the password is not being compared to what is stored. Try simplifying the comparePassword function and adding 'sync' to the bcrypt.compare which removes the need for a callback.
In models:
userSchema.methods.comparePassword = (candidatePassword) => {
return bcrypt.compareSync(candidatePassword, this.password);
};
I am facing a problem with the passport-facebook-token library. My node app.js looks like:
var passport = require('passport');
var FacebookTokenStrategy = require('passport-facebook-token');
app.use(passport.initialize());
app.use(passport.session());
passport.use(new FacebookTokenStrategy({
clientID: "myclientid",
clientSecret: "myclientsecret"
},
function(accessToken, refreshToken, profile, done) {
logger.info(JSON.stringify(accessToken));
logger.info(JSON.stringify(refreshToken));
logger.info(JSON.stringify(profile));
logger.info(JSON.stringify(done));
return done(null, user);
}
));
and prints:
info: "EAAH7GGhOLX4BAFY1cUQuTEUUtdAZAgAZAQr3S..."
info: undefined
info: {"provider":"facebook","id":"10153779813157657","displayName":"..."}}
info: undefined
In my auth.route.js:
var passport = require('passport');
//var strategy = require('passport-facebook-token');
router.post('/fabookauth', passport.authenticate('facebook-token'), function(req, res) {
// never called because the error occur into passport.authenticate
logger.info(JSON.stringify(req.body));
res.json({
email: req.body.email
});
});
when I call this endpoint, an undefined error prints on console.
Looking in passport-facebook-token source code, I think I found the problem:
https://github.com/drudge/passport-facebook-token/blob/master/src/index.js
_createClass(FacebookTokenStrategy, [{
key: 'authenticate',
value: function authenticate(req, options) {
var _this2 = this;
var accessToken = this.lookup(req, this._accessTokenField);
var refreshToken = this.lookup(req, this._refreshTokenField);
if (!accessToken) return this.fail({ message: 'You should provide ' + this._accessTokenField });
this._loadUserProfile(accessToken, function (error, profile) {
if (error) return _this2.error(error);
var verified = function verified(error, user, info) {
if (error) return _this2.error(error);
if (!user) return _this2.fail(info);
return _this2.success(user, info);
};
if (_this2._passReqToCallback) {
_this2._verify(req, accessToken, refreshToken, profile, verified);
} else {
_this2._verify(accessToken, refreshToken, profile, verified);
}
});
}
}
the function verified is always undefined.
I looked to many examples to do this job but I could not figure out the problem, am I doing something wrong? Is that a library bug?
this is probably some basic mistake but I am watching tutorial and even though I think I done everything exactly like I should after submitting login form I am redirected to the "failureRedirect" page. When I looked at source code in passport module I something.
After this:
Strategy.prototype.authenticate = function(req, options) {
options = options || {};
var username = lookup(req.body, this._usernameField) || lookup(req.query, this._usernameField);
var password = lookup(req.body, this._passwordField) || lookup(req.query, this._passwordField);
//I added:
console.log("U-> " + username);
console.log("P-> " + password);
console says
U-> null
P-> null
Then after this, rest is not executed.
if (!username || !password) {
return this.fail({ message: options.badRequestMessage || 'Missing credentials' }, 400);
}
I am not sure which parts of code should I post here. Maybe this can help
passport.use(new LocalStrategy(
function(username, password, done){
console.log("passport.use new LocalStrategy"); //never gets executed
//never gets executed
User.getUserByUsername(username, function(err, user){
if (err) throw err;
if(!user) {
console.log("Unknown user");
return done(null, false, {message: "Uknkown User"});
}
User.comparePassword(password, user.password, function(err, isMatch){
if (err) throw err;
if (isMatch) {
return done(null, user);
} else {
console.log("invalid Pass");
return done(null, false, {message: "Invalid Password"});
}
});
});
}));
router.post("/login", passport.authenticate("local", {failureRedirect:"/users/login/err", failureFlash:"invalid username or pass"}), function(req, res){
console.log("Authenticated OK");
req.flash("success", "You are logged in");
res.redirect("/xx");
});
I am not sure about the exact implementation that you are doing. Probably you are overriding the authenticate functionality using the prototype pattern.
However, Authentication using Passportjs is simple. I have done one recently in my side project. Please go through the below link with my own experience on implementing Passportjs
I have a well documented artcile that i wrote on my tech blog. Hope this helps you
// complete code for the exmaple node rest api authentication using passport
var express = require('express');
var passport = require('passport');
var passportHttp = require('passport-http');
var basicStrategy = passportHttp.BasicStrategy; // using the basic authentication
var app = express();
app.get('/',function(req,res){
res.send("There you go");
});
app.use(passport.initialize()); // initialize and use it in express
passport.use(new passportHttp.BasicStrategy(function(username,password,done) {
if(username === password){
done(null,username); //null means no error and return is the username
}
else{
return done(null,'there is no entry for you!'); // null means nothing to say,
//no error. 2nd is the custom statement business rule
}
}));
// this function hits first when there is an API call.
function ensureAuthenticated(req,res,next){
if(req.isAuthenticated()){
next();
// next redirects the user to the next api function waiting to be executed in the express framework
}else{
res.sendStatus(403); //forbidden || unauthorized
}
};
// this means all the API calls that hit via mydomain.com/api/ uses this authentication.
//session is false, because its a HTTP API call.
// setting this helps passport to skip the check if its an API call or a session web call
app.use('/api',passport.authenticate('basic',{session:false}));
// this is served the user once the authentication is a susccess
app.get('/api/data',ensureAuthenticated,function(req,res){
var somevalue = [{name: 'foo'},
{name: 'bar'},
{name: 'baz'}];
res.send(somevalue);
});
app.listen(3250);
console.log('listening to port on ' + 3250);