Reputation: 2599
I was trying out passport local authentication by following a tutorial. Everything seems fine but I'm getting this error when I make a request using Postman:
[nodemon] 1.18.11
[nodemon] to restart at any time, enter `rs`
[nodemon] watching: *.*
[nodemon] starting `node server.js`
body-parser deprecated bodyParser: use individual json/urlencoded middlewares server.js:17:10
(node:6336) DeprecationWarning: current URL string parser is deprecated, and will be removed in a future version. To use the new parser, pass option { useNewUrlParser: true } to MongoClient.connect.
Started listening on PORT: 8080
events.js:167
throw er; // Unhandled 'error' event
^
TypeError: Cannot read property 'password' of undefined
at model.userSchema.methods.validPassword.password [as validPassword] (F:\Web Projects\LocalAuth\userModel.js:20:50)
at F:\Web Projects\LocalAuth\passport.js:34:21
at F:\Web Projects\LT1Kqob5UDEML61gCyjnAcfMXgkdP3wGcgGdBcFel.js:4672:16
at F:\Web Projects\LT1Kqob5UDEML61gCyjnAcfMXgkdP3wGcgGdBcFry.js:4184:12
at process.nextTick (F:\Web Projects\LT1Kqob5UDEML61gCyjnAcfMXgkdP3wGcgGdBcFry.js:2741:28)
at process._tickCallback (internal/process/next_tick.js:61:11)
Emitted 'error' event at:
at F:\Web Projects\LT1Kqob5UDEML61gCyjnAcfMXgkdP3wGcgGdBcFel.js:4674:13
at F:\Web Projects\LT1Kqob5UDEML61gCyjnAcfMXgkdP3wGcgGdBcFry.js:4184:12
at process.nextTick (F:\Web Projects\LT1Kqob5UDEML61gCyjnAcfMXgkdP3wGcgGdBcFry.js:2741:28)
at process._tickCallback (internal/process/next_tick.js:61:11)
[nodemon] app crashed - waiting for file changes before starting...
Here is my user schema:
const mongoose = require('mongoose');
const bcrypt = require('bcrypt-nodejs');
const Config = require ('./config');
mongoose.connect (Config.dbUrl);
let userSchema = new mongoose.Schema({
local : {
email: String,
password: String,
},
});
userSchema.methods.generateHash = password => {
return bcrypt.hashSync(password, bcrypt.genSaltSync(8), null);
};
userSchema.methods.validPassword = password => {
return bcrypt.compareSync(password, this.local.password);
};
module.exports = mongoose.model('User', userSchema);
And this is my server.js file:
const express = require ('express');
const session = require ('express-session');
const mongoose = require ('mongoose');
const bodyParser = require ('body-parser');
const cookieParser = require ('cookie-parser');
const morgan = require ('morgan');
const flash = require ('connect-flash');
const passport = require ('passport');
const PassHandler = require('./passport');
const app = express ();
const port = process.env.PORT || 8080;
app.use (morgan ('dev'));
app.use (bodyParser ({extended: false}));
app.use (cookieParser ());
app.use (
session ({secret: 'borkar.amol', saveUninitialized: true, resave: true})
);
//Initialize Passport.js
app.use (passport.initialize ());
app.use (passport.session ());
app.use (flash ());
//Global Vars for flash messages
app.use((req, res, next) => {
res.locals.successMessage = req.flash('successMessage');
res.locals.errorMessage = req.flash('errorMessage');
res.locals.error = req.flash('error');
next();
});
PassHandler(passport);
//Middleware to check if the user is logged in.
const isLoggedIn = (req, res, next) => {
if(req.isAuthenticated()) {
return next();
}
res.status(400).json({ message: 'You are not authenticated to acces this route.' });
}
app.get('/', (req, res) => {
res.json({ message: 'Local Auth API v0.1.0'});
});
app.post('/signup', passport.authenticate('local-signup', {
successRedirect: '/user',
failureRedirect: '/signup',
failureFlash: true,
}));
app.post('/login', passport.authenticate('local-login', {
successRedirect: '/user',
failureRedirect: '/',
failureFlash: true,
}));
app.get('/user', isLoggedIn, (req, res) => {
res.json({ user: req.user, message: "User is logged in."});
});
app.listen (port, () => {
console.log (`Started listening on PORT: ${port}`);
});
Here is the Passport strategy I'm using:
passport.use (
'local-login',
new LocalStrategy (
{
usernameField: 'email',
passwordField: 'password',
passReqToCallback: true,
},
function (req, email, password, done) {
User.findOne ({'local.email': email}, function (err, user) {
if (err) return done (err);
if (!user)
return done (
null,
{message: 'User not found.'},
req.flash ('errorMessage', 'No user found.')
);
if (!user.validPassword (password))
return done (
null,
{message: 'Invalid email or password.'},
req.flash ('errorMessage', 'Oops! Wrong password.')
);
// all is well, return successful user
return done (null, user);
});
}
)
);
I've no idea whats going wrong to be honest. please help.
**Update:**The signup route and signup strategy is working fine. Only the /login
route is giving problem.
Upvotes: 2
Views: 1996
Reputation: 1
"In classic function expressions, the "this" keyword is bound to different values based on the context in which it is called. With arrow functions however, this is lexically bound. It means that it uses "this" from the code that contains the arrow function." - Quote from freeCodeCamp
Here, we are using "this" from the userSchema instead of the "this" from arrow function itself. So, you should change your arrow function to classic function expression.
Upvotes: 0
Reputation: 14
define it as a function instead
before
userSchema.methods.validPassword = password => {
return bcrypt.compareSync(password, this.local.password);
};
after
userSchema.methods.validPassword = function (password) {
return bcrypt.compareSync(password, this.local.password);
};
i dont know why it doesn't work with arrow callback
Upvotes: 0
Reputation: 11
I faced the same exact issue. I solved mine by moving the validatePassword method from the user schema to the passport strategy. It seemed the password was not being passed to the validatePassword method in the UserSchema.
Here is my UserSchema.js
// Pulling in required dependencies
const mongoose = require('mongoose');
const bcrypt = require('bcrypt-nodejs');
const Schema = mongoose.Schema;
//Creat UserSchema
const UserSchema = new Schema({
local: {
email: String,
password: String
},
role: {
type: String,
default: 'user',
},
books_downloaded: {
booksId: {
type: Array,
required: false,
},
},
books_needed: {
type: Object,
default: null,
},
created_at: {
type: Date,
default: Date.now,
},
});
// methods=====================================================
// generating a hash
UserSchema.methods.generateHash = (password) => {
return bcrypt.hashSync(password, bcrypt.genSaltSync(8), null);
}
// expose User model to the app
module.exports = mongoose.model('User', UserSchema);
And here is my passport strategy
// load all the things we need
const LocalStrategy = require('passport-local').Strategy;
const bcrypt = require('bcrypt-nodejs');
//load up the user model
const User = require('../models/User');
// expose this function to our app
module.exports = passport => {
/**
* passport session setup =======================
* required for persistent login sessions
* serialize and unserialize users out of session
*/
//serialize the user for the session
passport.serializeUser((user, done) => {
done(null, user.id);
});
//deserialize the user
passport.deserializeUser((id, done) => {
User.findById(id, (err, user) => {
done(err, user);
});
});
/**
* LOCAL SIGNUP
* using named strategies
*/
// local signup
passport.use(
'local-signup',
new LocalStrategy(
{
usernameField: 'email',
passwordField: 'password',
passReqToCallback: true,
},
(req, email, password, done) => {
process.nextTick(() => {
// find a user whose email is the same as the forms email
User.findOne({ 'local.email': email }, (err, user) => {
if (err) return done(err);
// check to see if theres already a user with that email
if (user) {
return done(null, false, req.flash('signupMessage', 'That email is already taken.'));
} else {
// if there is no user with that email
// create the user
var newUser = new User();
// set the user's local credentials
newUser.local.email = email;
newUser.local.password = newUser.generateHash(password);
// save the user
newUser.save(err => {
if (err) throw err;
return done(null, newUser);
});
}
});
});
}
)
);
// =========================================================================
// LOCAL LOGIN =============================================================
passport.use(
'local-login',
new LocalStrategy(
{
usernameField: 'email',
passwordField: 'password',
passReqToCallback: true,
},
(req, email, password, done) => {
// checking to see if the user trying to login already exists
User.findOne({ 'local.email': email }, function(err, user) {
// if there are any errors, return the error before anything else
if (err) return done(err);
// if no user is found, return the message
if (!user) return done(null, false, req.flash('loginMessage', 'No
user found.'));
// if the user is found but the password is wrong
let correctPassword =
bcrypt.compareSync(password,user.local.password);
if (!correctPassword)
return done(null, false, req.flash('loginMessage', 'Oops!
Wrong password.'));
// If all is well, return successful user
return done(null, user);
});
}
)
);
};
Upvotes: 1
Reputation: 2599
I rewrote most of the script, and it works now! Here is the code.
const express = require ('express');
const session = require ('express-session');
const bodyParser = require ('body-parser');
const cookieParser = require ('cookie-parser');
const mongoose = require ('mongoose');
const passport = require ('passport');
const LocalStrategy = require ('passport-local').Strategy;
const User = require ('./UserModel');
const dbUrl = 'url';
const port = process.env.PORT || 9000;
const app = express ();
app.use (bodyParser.json ());
app.use (bodyParser.urlencoded ({extended: true}));
app.use (cookieParser ());
app.use (
session ({secret: 'borkar.amol', saveUninitialized: false, resave: false})
);
app.use (passport.initialize ());
app.use (passport.session ());
mongoose.connect (dbUrl, {useNewUrlParser: true}, () => {
console.log ('Successfully connected to hosted database.');
});
passport.serializeUser ((User, done) => {
//console.log ('SERIALIZEUSER: ', User._id);
done (null, User._id);
});
passport.deserializeUser ((id, done) => {
User.findById (id, (err, User) => {
//console.log ('DESERIALIZEUSER: ', User);
done (err, User);
});
});
passport.use (
'signup',
new LocalStrategy (
{
usernameField: 'email',
passwordField: 'password',
},
(email, password, done) => {
process.nextTick (() => {
User.findOne ({email: email}, (err, foundUser) => {
if (err) return done (err);
if (foundUser) {
return done (null, false, {
message: 'The email is already registered.',
});
} else {
let newUser = new User ();
newUser.email = email;
newUser.password = newUser.hashPassword (password);
newUser.save (err => {
if (err) console.error ('Error when writing to database', err);
});
return done (null, newUser, {
message: 'User has been registered successfully.',
});
}
});
});
}
)
);
passport.use (
'login',
new LocalStrategy (
{
usernameField: 'email',
},
(email, password, done) => {
User.findOne ({email: email}, (err, foundUser) => {
if (err) return done (err);
if (!foundUser) {
return done (null, false, {
message: 'Invalid Username or Password.',
});
}
if (!foundUser.comparePassword (password)) {
return done (null, false, {
message: 'Invalid Username or Password.',
});
}
return done (null, foundUser);
});
}
)
);
//Routes --->
app.get ('/user', (req, res, next) => {
if (req.isAuthenticated ()) {
res.json ({user: req.user});
return next ();
}
res.status (400).json ({message: 'Request is not authenticated.'});
});
app.post (
'/signup',
passport.authenticate ('signup', {
successRedirect: '/user',
failureMessage: true,
successMessage: true,
})
);
app.post (
'/login',
passport.authenticate ('login', {
successRedirect: '/user',
failureMessage: true,
successMessage: true,
})
);
app.post ('/logout', (req, res) => {
if (req.user) {
req.session.destroy ();
req.logout ();
res.clearCookie ('connect.sid');
return res.json ({message: 'User is now logged out.'});
} else {
return res.json ({message: 'Error: No user to log out.'});
}
});
app.listen (port, () => {
console.log (`Started listening on PORT: ${port}`);
});
Upvotes: 0