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

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);

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

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');

Passport simple authentification with hashed password on an API

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;

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.

Passport.authenticate not sending a response

I'm using Passport for authentication, specifically with a JWT strategy. I'm able to create a new token when a user is created, however, when I use that token in the header of a request to a route that requires authentication, my request just hangs up. I'm using Postman to test these POST/GET requests.
Here's my initial configuration for signing up a user:
const User = require('../db/models/User');
const jwt = require('jsonwebtoken');
function userToken(user) {
return jwt.sign({
id: user.id,
}, process.env.JWT_SECRET);
}
exports.signup = function(req, res, next) {
const email = req.body.email.toLowerCase();
const password = req.body.password.toLowerCase();
User.findOne({
where: { email },
}).then(function(user) {
if (!user) {
User.create({
email,
password,
})
.then(function(user) {
return res.send({ token: userToken(user) });
});
}
if (user) {
return res.send({ message: 'That user is in use' });
}
});
};
Here's my passport configuration:
const passport = require('passport');
const JwtStrategy = require('passport-jwt').Strategy;
const ExtractJwt = require('passport-jwt').ExtractJwt;
const User = require('../db/models/User');
const jwtOptions = {
jwtFromRequest: ExtractJwt.fromHeader('authorization'),
secretOrKey: process.env.JWT_SECRET,
};
const jwtLogin = new JwtStrategy(jwtOptions, function(payload, done) {
User.findOne({
where: { id: payload.id },
}, function(err, user) {
if (err) { return done(err, false); }
if (user) { return done(null, user); }
return done(null, false);
});
});
passport.use(jwtLogin);
Here's what my protected route looks like:
const passport = require('passport');
const requireAuth = passport.authenticate('jwt', { session: false });
module.exports = function router(app) {
app.get('/', requireAuth, function(req, res) {
res.send({ 'hi': 'there' });
});
};
Here's what I see in my terminal:
Executing (default): SELECT "id", "username", "email", "password", "photo", "createdAt", "updatedAt" FROM "users" AS "user" WHERE "user"."id" = 15;
So I know that it's correctly querying for a user id and searching for it, however, it just hangs up at this point, rather than serving me a response.
Not sure what the issue is, so any and all suggestions are welcomed and appreciated. Thank you!
Realized that because I am using Sequelize, it handles errors with a catch like so:
...
const jwtLogin = new JwtStrategy(jwtOptions, function(payload, done) {
User.findOne({
where: { id: payload.id }
})
.then(user => {
if (user) {
done(null, user);
} else {
done(null, false);
}
})
.catch(err => {
if (err) { return done(err, false); }
});
});
...
This solved my issue and is returning my response.

Resources