I'm building a back end with Express and Postgres, with Passport.js to authenticate users and Swagger UI Express to generate the API documentation. However, I'm having trouble with Passport's basic strategy, which I need to implement to allow users to log in on the Swagger UI and test certain endpoints.
This is my Passport.js configuration (click here for full repo):
// ./routers/auth.js
const passport = require("passport");
const authenticate = async (email, password, done) => {
try {
const result = await pool.query("SELECT users.id AS id, users.email AS email, users.password AS password, carts.id AS cart_id FROM users JOIN carts ON carts.user_id = users.id WHERE email = $1", [email]);
if (result.rows.length === 0) return done(null, false);
const passwordMatch = await bcrypt.compare(password, result.rows[0].password);
if (!passwordMatch) return done(null, false);
return done(null, { id: result.rows[0].id, email: result.rows[0].email, cartId: result.rows[0].cart_id });
} catch(err) {
return done(err);
}
}
const LocalStrategy = require("passport-local").Strategy;
passport.use(new LocalStrategy({ usernameField: "email" }, authenticate));
const { BasicStrategy } = require("passport-http");
passport.use(new BasicStrategy({ usernameField: "email" }, authenticate));
passport.serializeUser((user, done) => {
return done(null, user.id);
});
passport.deserializeUser(async (id, done) => {
try {
const result = await pool.query("SELECT users.id AS id, users.email AS email, carts.id AS cart_id FROM users JOIN carts ON carts.user_id = users.id WHERE users.id = $1", [id]);
if (result.rows.length === 0) return done(null, false);
return done(null, { id: result.rows[0].id, email: result.rows[0].email, cartId: result.rows[0].cart_id });
} catch(err) {
return done(err);
}
});
I've been looking around the Internet for potential solutions, including adding an authorization header in my Swagger options for Swagger UI Express, but nothing I've found has worked so far. Help with this would be greatly appreciated.
Related
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);
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');
I am trying to set up Passport with Express and MongoDB. At the moment I am able to register users in the database. But whenever I try to login, I get an error saying that data and hash arguments are required. Right now I have my Server.js file like this
const mongoose = require('mongoose');
const User = require('./models/users')
const initializePassport = require('./passport-config')
initializePassport(
passport,
email => User.find({email: email}),
id => User.find({id: id})
)
app.post('/register', checkNotAuthenticated, async (req, res) => {
try {
const hashedPassword = await bcrypt.hash(req.body.password, 10)
const newUser = new User({
id: Date.now().toString(),
name: req.body.name,
email: req.body.email,
password: hashedPassword
})
res.redirect('/login')
console.log(newUser)
} catch {
res.redirect('/register')
}
And my Passport-Config.js file like this `
const LocalStrategy = require('passport-local').Strategy
const bcrypt = require('bcrypt');
const User = require('./models/users')
function initialize(passport, getUserByEmail, getUserById) {
const authenticateUser = async (email, password, done) => {
const user = getUserByEmail(email)
if (user === null) {
return done(null, false, { message: 'No user with that email' })
}
try {
if (await bcrypt.compare(password, user.password)) {
return done(null, user)
} else {
return done(null, false, { message: 'Password incorrect' })
}
} catch (e) {
return done(e)
}
}
passport.use(new LocalStrategy({ usernameField: 'email' }, authenticateUser))
passport.serializeUser((user, done) => done(null, user.id))
passport.deserializeUser((id, done) => {
return done(null, User.findById({user: id}))
})
}
`
I've done some investigation using console.log() statements (not proud of it) but I think I've managed to find out the issue. If we add in the the first console log statement here:
app.post('/register', checkNotAuthenticated, async (req, res) => {
try {
console.log("BCRYPT COMPARE RUNS HERE")
const hashedPassword = await bcrypt.hash(req.body.password, 10)
const newUser = new User({
id: Date.now().toString(),
name: req.body.name,
email: req.body.email,
password: hashedPassword
})
res.redirect('/login')
console.log(newUser)
} catch {
res.redirect('/register')
}
and the second one here:
const initializePassport = require('./passport-config')
initializePassport(
passport,
email => User.find({email: email}).then((result) => { console.log("USER DATA EXTRACTED HERE") }).catch((err) => { console.log(err) }),
id => User.find({id: id})
)
The next time you click on login, you should see an output like:
Listening on port 3000
BCRYPT COMPARE HAPPENING
Error: data and hash arguments required
...
...
...
USER DATA EXTRACTED HERE
Notice that bcrypt.compare is being run before we are actually able to grab the user information from the DB? This means that all the arguments into that function are null, which is what is returning that error. Now, I'm no JS expert, but this can be fixed with an await statement added here:
function initialize(passport, getUserByEmail, getUserById) {
const authenticateUser = async (email, password, done) => {
const user = await getUserByEmail(email)
if (user === null) {
return done(null, false, { message: 'No user with that email' })
}
Which makes sure that the user info is queried from the DB before moving along in the script.
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;
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.