Passport simple authentification with hashed password on an API - node.js

I try to authentificate on a /login route with passport, I give an email, and a password (already stored in the database -email + hashed password with bcrypt). However, when I try to authentificate, my code never go into the passport.use...
const passport = require("passport");
const LocalStrategy = require("passport-local").Strategy;
const db = require("../config/database");
/* Route methods */
exports.login = (req, res) => {
const email = req.body.email;
const password = req.body.password;
console.log("It will be displayed");
passport.use(
new LocalStrategy(function(email, password, done) {
console.log("It won't");
db.User.findOne({ email: email }, function(err, user) {
if (err) {
return done(err);
}
if (!user) {
return done(null, false, { message: "Incorrect email." });
}
if (!bcrypt.compareSync(password, user.dataValues.password)) {
return done(null, false, { message: "Incorrect password." });
}
return done(null, user);
});
})
);
};
Furthermore, I create an API, and I'm wondering how authentificate someone (with session) with a REST API. Do I have to send on a endpoint the email and the password, then I create a session ? Thank you if you have any ressources.

This happens because you have only declared the actual Strategy but you'll need to create a separate route where you will authenticate the user using this local strategy with passport.authenticate.
Take a look at the example app I've created: https://github.com/BCooperA/express-authentication-starter-api
In config/passport.js I've created the actual Strategy:
const passport = require('passport')
, mongoose = require('mongoose')
, User = mongoose.model('User')
, LocalStrategy = require('passport-local').Strategy;
/**
|--------------------------------------------------------------------------
| Local authentication strategy (email, password)
|--------------------------------------------------------------------------
*/
passport.use(new LocalStrategy({ usernameField: 'user[email]', passwordField: 'user[password]' },
function(email, password, done) {
User.findOne({email: email}, function (err, user) {
if(err)
return done(err);
// incorrect credentials
if (!user || !user.validPassword(password) || user.password === '') {
return done(null, false, { errors: [{ msg: "Incorrect credentials" }] });
}
// inactive account
if(user.activation_token !== '' || user.active === 0) {
// account is not activated
return done(null, false, { errors: [{ msg: "Inactive account" }] });
}
// all good
return done(null, user);
});
}));
In addition, I've also created a separate POST route for signing in users locally where I'm using passport.authenticate.
In routes/auth.routes.js:
const router = require('express').Router()
, mongoose = require('mongoose')
, User = mongoose.model('User')
, passport = require('passport');
/**
|--------------------------------------------------------------------------
| Local Authentication
|--------------------------------------------------------------------------
| Authenticates user using Local Strategy by Passport
*/
router.post('/signin', function(req, res, next) {
if(req.body.user.email === '' || req.body.user.password === '')
// overrides passports own error handler
return res.status(422).json({errors: [{ msg: 'Missing credentials'}]});
passport.authenticate('local', { session: false }, function(err, user, info) {
if(err)
return next(err);
if(user) {
// generate JSON web token to user
user.token = user.generateJWT();
// return user object
return res.status(200).json({ user: user.toAuthJSON() });
} else {
// return any errors
return res.status(422).json(info);
}
})(req, res, next);
});
module.exports = router;

Related

login works even if the password incorrect using passportJs

I am trying to build authentication system for my website , and I am using express to build server , and passport local strategy for authentication ,
let passport = require("passport");
let LocalStrategy = require("passport-local").Strategy;
let session = require("express-session");
app.use(session({ secret: "super secret" }));
app.use(passport.initialize());
app.use(passport.session());
passport.use(
new LocalStrategy(function (username, password, done) {
User.findOne({ username: username }, function (err, user) {
if (err) {
return done(err);
}
if (!user) {
return done(null, false);
}
return done(null, user);
});
})
);
when I signup , everything works and hashed password save in the database :
app.post("/signup", async (req, res) => {
let { fullname, email, username, password } = req.body;
let user = new User({ fullname, email, username });
await User.register(user, password);
res.redirect("/login");
});
but when I try to login , the user logins if the username is exists regardless the password :
app.post(
"/login",
passport.authenticate("local", {
failureFlash: true,
failureRedirect: "/login",
}),
async (req, res) => {
let user = await User.findById(req.user._id);
if (user.lastLogin === undefined) {
await User.updateOne({ _id: user._id }, { lastLogin: Date.now() });
res.redirect("/final-step");
}
res.redirect("/hi");
}
);
Hope to help me , thanks
I solved the problem by replace this code :
passport.use(
new LocalStrategy(function (username, password, done) {
User.findOne({ username: username }, function (err, user) {
if (err) {
return done(err);
}
if (!user) {
return done(null, false);
}
return done(null, user);
});
})
);
by :
passport.use(new LocalStrategy(User.authenticate()));
I had the same issue, I solved it by adding
{ strictQuery: false }
in the schema like this:
new Schema({}, {
strictQuery: false
})
after that it was returning correct values

How can I add a simple middleware that will verify JWT and be ensure that is authorized?

I have the following middleware that works for authentication with JWT and passport.js. The thing is that I also need somehow verify for all controllers if the user is admin or not. I am using this passport.js middleware for authentication:
if (typeof app == "function") {
app.use(passport.initialize());
app.use(passport.session());
passport.serializeUser(function (user, done) {
done(null, JSON.stringify(user));
});
passport.deserializeUser(function (user, done) {
done(null, JSON.parse(user));
});
var opts = {};
opts.jwtFromRequest = passportJwtExctract.fromAuthHeaderAsBearerToken();
opts.secretOrKey = process.env.JWT_SECRET;
passport.use(
new passportJwtStrategy(opts, async (jwt_payload, done) => {
var user = await User.findByPk(jwt_payload.id);
if (user === null) {
return done(null, false, {
message: "Sorry, we couldn't find an account.",
});
}
done(null, user);
await User.update(
{ last_signin_date: "now()" },
{
where: {
id: user.id,
},
}
);
return;
})
);
passport.use(
new passportLocalStrategy(
{
usernameField: "email",
passwordField: "password",
},
function (username, password, done) {
process.nextTick(async function () {
var valid =
validator.isEmail(username) && validator.isLength(password, 8);
if (!valid) {
return done(null, false, {
message: "Incorrect username or password",
});
}
username = username.toLowerCase();
let user = await User.findOne({ where: { email: username } });
user = user.toJSON();
if (user === undefined) {
return done(null, false, {
message: "Sorry, we couldn't find an account with that email.",
});
}
var hashed_password = await bcrypt.hash(password, user.salt);
if (hashed_password == user.password) {
delete user.password;
delete user.salt;
user.user_mode = process.env.USER_MODE;
user.token = jwtLib.sign(user, process.env.JWT_SECRET);
//l('done user', user)
done(null, user);
await User.update(
{ last_signin_date: "now()" },
{
where: {
id: user.id,
},
}
);
return;
}
return done(null, false, {
message: "Sorry, that password isn't right.",
});
});
}
)
);
}
How can I verify JWT correctly for all related requests and be sure that the user is admin? Something like the bellow option.
Common.ensureAuthenticated("Administrator"),
you can investigate about Outh2 authentication where in JWT token you can claim number of parameter according to need and at time of verification you can validate it and extract and use it everywhere you want !
For admin and different different role you can define "actor" as key and its role in respect to priority as value and create a check actor flag at run time !
I'm assuming your application starts from index.js In index.js, you can use a middleware before initiating your routes.
For example:
const express = require('express');
const app = express();
const router = require('./src/router'); // Your router file (router.js)
app.use(AuthMiddleware); // This is how you use your middleware
app.use('/', router);

passport local not working for multiple model

I have used passport local for authenticate purpose but not working.It is working only one from these two(user and admin).I do not know why it is working like this.If anyone know please help to find solution.
passportconfig.js:
const passportuser = require('passport');
const localStrategyuser = require('passport-local').Strategy;
const mongooseuser = require('mongoose');
const passportAdmin = require('passport');
const localStrategyAdmin = require('passport-local').Strategy;
const mongooseadmin = require('mongoose');
var User = mongooseuser.model('User');
var Admin = mongooseadmin .model('Admin');
passportuser.use(
new localStrategyuser({ usernameField: 'email' },
(username, password, done) => {
User.findOne({ email: username },
(err, user) => {
if (err) { return done(err); } else if (!user) {
return done(null, false, { message: 'Email is not registered for User' });
} else if (!user.verifyPassword(password)) {
return done(null, false, { message: 'Wrong password.' });
} else {
return done(null, user);
}
});
})
);
passportAdmin.use(
new localStrategyAdmin({ usernameField: 'email' },
(username, password, done) => {
Admin.findOne({ email: username },
(err, admin) => {
if (err)
return done(err);
// unknown user
else if (!admin)
return done(null, false, { message: 'Email is not registered for Admin' });
// wrong password
else if (!admin.verifyPassword(password))
return done(null, false, { message: 'Wrong password.' });
// authentication succeeded
else
return done(null, admin);
});
})
);
Like #Panther mentioned in the comment, passportuser and passportAdmin is just the same module, you have to create separate Passport instances instead of using the default one
const { Passport } = require('passport');
const passportuser = new Passport();
const passportAdmin = new Passport();
And also as #Panther mentioned, there is no need to require('mongoose') multiple times. This would work equally well:
const mongoose = require('mongoose');
const User = mongoose.model('User');
const Admin = mongoose.model('Admin');

Is there a way of defining what collection to use when using passport.authenticate

Within my project I have both hcpuser and regular user. I have got the registration working for HCP but when i go to do my login function it still only reads from my users collection and not from the hcpuser I want it to. Is there a simple line of code I can declare before my function that allows this.
Hcp model:
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var bcrypt = require('bcryptjs');
var User = require('../model/user.model').schema
var schema = new Schema({
email : {type:String, require:true},
username: {type:String, require:true},
password:{type:String, require:true},
creation_dt:{type:Date, require:true},
hcp : {type:Boolean, require : true},
clinic:{type:String, require:true},
patients: [User],
});
schema.statics.hashPassword = function hashPassword(password){
return bcrypt.hashSync(password,10);
}
schema.methods.isValid = function(hashedpassword){
return bcrypt.compareSync(hashedpassword, this.password);
}
schema.set('collection', 'hcpuser');
module.exports = mongoose.model('Hcpuser',schema);
Hcp controller with first register function working as expected.
const express = require("express");
const mongoose = require("mongoose");
const cors = require("cors");
const router = express.Router();
const Hcpusermodel = mongoose.model("Hcpuser")
const {ObjectId} = require("mongodb");
var Hcpuser = require('../model/hcp.model')
var passport = require('passport');
router.post('/register', function (req, res, next) {
addToDB(req, res);
});
async function addToDB(req, res) {
var hcpuser = new Hcpuser({
email: req.body.email,
hcp : true,
username: req.body.username,
password: Hcpuser.hashPassword(req.body.password),
clinic: req.body.clinic,
creation_dt: Date.now()
});
try {
doc = await hcpuser.save();
return res.status(201).json(doc);
}
catch (err) {
return res.status(501).json(err);
}
}
//login
router.post('/login', function(req,res,next){
passport.authenticate('local', function(err, hcpuser, info) {
if (err) { return res.status(501).json(err); }
if (!hcpuser) { return res.status(501).json(info); }
req.logIn(hcpuser, function(err) {
if (err) { return res.status(501).json(err); }
return res.status(200).json({message:'Login Success'});
});
})(req, res, next);
});
From your question, you either want to auth one OR the other, or check both - I think you're asking for how to auth separately (one OR the other, not both)?
please note, this specific code has been untested, but the principles are there and still stand.
One OR the Other
You need to define the name of each strategy in your passport code.
For example:
passport.use('users', new LocalStrategy({
usernameField: 'user[email]',
passwordField: 'user[password]',
},(email, password, done) => {
Users.findOne({ email })
.then((user) => {
if(!user || !user.validatePassword(password)) {
return done(null, false, { errors: { 'email or password' : 'is valid' } });
}
return done(null, user);
}).catch(done);
}));
passport.use('hcpusers', new LocalStrategy({
usernameField: 'user[email]',
passwordField: 'user[password]',
},(email, password, done) => {
HCPUser.findOne({ email })
.then((user) => {
if(!user || !user.validatePassword(password)) {
return done(null, false, { errors: { 'email or password' : 'is valid' } });
}
return done(null, user);
}).catch(done);
}));
And then in your passport.authenticate method, specify the strategy name:
passport.authenticate('users', function(err, user, info) { ...
and
passport.authenticate('hcpusers', function(err, user, info) { ...
In this case you'll need two separate endpoints for each login method, or just an extra parameter specifying which one to check from an if statement.
Update
For your comment of not knowing where the passport code should be, this is up to you. However, I like to keep passport code in an 'auth' folder and add the following code to a passport.js file:
const mongose = require('mongoose');
const passport = require('passport');
const LocalStrategy = require('passport-local');
const Users = mongose.model('Users');
passport.use('...', new LocalStrategy({
...
...
}));
Include this in your server/index/app.js (whatever yours is) app.use(passport.initialize());
You can then just use the passport code as normal in your user controllers.
My passport.authenticate code looks like:
return passport.authenticate('local', function(err, passUser, info) {
if (err) {
return next(err);
}
if (!passUser) {
return res.status(503).send('error');
}
const user = passUser;
user.token = user.generateJWT();
return res.json({ token: user.token });
})(req, res, next);
But this can be different for you (i.e. you may not be using sessions?) Either way, if authenticated, just send the response to client so it can proceed.
Hi so to solve this issue i followed what was mentioned. I needed to define the name of the collection within the hcp model using this:
module.exports = mongoose.model('Hcpuser', Hcpuser, 'Hcpuser');
I then created a local strategy ensuring that i was searching using the right model which would then point to the right collection within my DB.
Solution:
var passport = require('passport')
, LocalStrategy = require('passport-local').Strategy;
passport.use('hcplocal', new LocalStrategy(
function(uemail, password, done) {
Hcpuser.findOne({ "email" : uemail }, function(err, user) { console.log(user)
if (err) { return done(err); }
if (!user) {
console.log(user);
console.log(err);
console.log(uemail)
return done(null, false, { message: 'Incorrect email.' });
}
if (!user.isValid(password)) {
console.log(user);
return done(null, false, { message: 'Incorrect password.' });
}
return done(null, user);
});
}
));
router.post('/login',function(req,res,next){
passport.authenticate('hcplocal', function(err, user, info) {
if (err) { return res.status(501).json(err); }
if (!user) { return res.status(501).json(info); }
req.logIn(user, function(err) {
if (err) { return res.status(501).json(err); }
console.log(user);
return res.status(200).json({message:'Login Success'});
});
})(req, res, next);
});

Unauthorized after login with passport-jwt

I used passport-jwt for login. it find the user and the user can login but when after logging in, I want to redirect to the user dashboard nothing happens and it shows Unauthorized the in browser when it redirect to localhost:8080/my/
can you find out what the problem is:
p.s: when I remove passport.authenticate('jwt', {session: false}) from my.js it works and it redirects to /my/. so I think the problem is with this part of code or JWTStrategy in auth.js. I don't know how to fix it!
here's my code : (sorry it's to much code)
auth.js:
//Create a passport middleware to handle User login
passport.use(new LocalStrategy({
usernameField: 'username',
passwordField: 'password'
}, async (username, password, done) => {
try {
//Find the user associated with the email provided by the user
await User.findOne({
username: username
}, async function (err, user) {
if (!user) {
//If the user isn't found in the database, return a message
return done(null, false, {
message: 'User not found'
});
}
//Validate password and make sure it matches with the corresponding hash stored in the database
//If the passwords match, it returns a value of true.
let validate = await user.isValidPassword(password);
if (!validate) {
return done(null, false, {
message: 'Wrong Password'
});
}
//Send the user information to the next middleware
return done(null, user, {
message: 'Logged in Successfully'
});
});
} catch (error) {
return done(error);
}
}));
passport.use(new JWTStrategy({
jwtFromRequest : ExtractJWT.fromAuthHeaderAsBearerToken(),
secretOrKey : 'secret_token'
}, function(jwt_payload, done) {
User.findOne({id: jwt_payload.sub}, function(err, user) {
if (err) {
return done(err, false);
}
if (user) {
return done(null, user);
} else {
return done(null, false);
// or you could create a new account
}
});
}));
authenticate.js :
router.post('/login', async (req, res, next) => {
passport.authenticate('local', async (err, user, info) => {
try {
if (err || !user) {
const error = new Error('An Error occured')
return next(error);
}
req.login(user, {
session: false
}, async (error) => {
if (error) return next(error)
//We don't want to store the sensitive information such as the
//user password in the token so we pick only the email and id
const body = {
_id: user._id,
username: user.username
};
//Sign the JWT token and populate the payload with the user email and id
const token = jwt.sign({
user: body
}, 'top_secret');
//Send back the token to the user
// return res.json({
// token
// });
res.redirect('/my/?token='+token);
});
} catch (error) {
return next(error);
}
})(req, res, next);
});
module.exports = router;
user.js
const mongoose = require('mongoose');
const Role = require('./role');
const bcrypt = require('bcrypt');
let Schema = mongoose.Schema;
let userSchema = new Schema({
.
.
.
});
userSchema.pre('save', async function(next){
//'this' refers to the current document about to be saved
const user = this;
//Hash the password with a salt round of 10, the higher the rounds the more secure, but the slower
//your application becomes.
const hash = await bcrypt.hash(this.password, 10);
//Replace the plain text password with the hash and then store it
this.password = hash;
//Indicates we're done and moves on to the next middleware
next();
});
userSchema.methods.isValidPassword = async function(password){
const user = this;
//Hashes the password sent by the user for login and checks if the hashed password stored in the
//database matches the one sent. Returns true if it does else false.
let compare= await bcrypt.compare(password , user.password);
return compare;
}
module.exports = mongoose.model('User' , userSchema);
my.js
//secure routes that only users with verified tokens can access.
//Lets say the route below is very sensitive and we want only authorized users to have access
//Displays information tailored according to the logged in user
router.get('/',passport.authenticate('jwt', {session: false}), (req, res) => {
//We'll just send back the user details and the token
res.json({
message : 'You made it to the secure route',
token : req.query.token
});
})
module.exports = router;
This problem usually occurs because of an invalid JWT. To authenticate a JWT with passport-jwt it has to be of the following format:
"JWT xxxx.xxxx.xxxx"
So maybe try changing the following line in your Authenticate.js file:
res.redirect('/my/?token='+token);
// CHANGE IT TO:
res.redirect('/my/?token=' + 'JWT ' + token);
I hope this solves your problem.

Resources