How to use passport.js on nodejs with a sqlite database - node.js

Hello I am quite new to nodejs and this is my first question. I want to add a user login to my express server. So I tried the passport.js example (express-4.x-local-example). Now I tried to store users in my sqlite database. I oriented on other question (node.js passport autentification with sqlite). But that didn't solved my problem. I don't want to create a live application with it, I just want to understand how it works. When I add a user to my database and try to login, I always get directed to /bad.
Here is the code I wrote:
The database.js
const sqlite3 = require("sqlite3");
const sqlite = require("sqlite");
const fs = require("fs").promises;
async function provideDatabase() {
const databaseExists = await fs
.access("./.data/database.db")
.catch(() => false);
const db = await sqlite.open({
filename: "./.data/database.db",
driver: sqlite3.Database
});
if (databaseExists === false) {
await db.exec(
"CREATE TABLE users (id INTEGER PRIMARY KEY AUTOINCREMENT, username TEXT, password TEXT, salt TEXT)"
);
}
return db;
}
module.exports = provideDatabase;
And here is my server.js
var express = require('express');
var passport = require('passport');
var Strategy = require('passport-local').Strategy;
const provideDatabase = require("./database");
const database = provideDatabase();
const LocalStrategy = require('passport-local').Strategy
const bodyParser = require("body-parser");
var app = express();
app.use(bodyParser.json());
var crypto = require('crypto');
function hashPassword(password, salt) {
var hash = crypto.createHash('sha256');
hash.update(password);
hash.update(salt);
return hash.digest('hex');
}
passport.use(new LocalStrategy(async function(username, password, done) {
const db = await database;
db.get('SELECT salt FROM users WHERE username = ?', username, function(err, row) {
if (!row) return done(null, false);
var hash = hashPassword(password, row.salt);
db.get('SELECT username, id FROM users WHERE username = ? AND password = ?', username, hash, function(err, row) {
if (!row) return done(null, false);
return done(null, row);
});
});
}));
passport.serializeUser(function(user, done) {
return done(null, user.id);
});
passport.deserializeUser(async function(id, done) {
const db = await database;
db.get('SELECT id, username FROM users WHERE id = ?', id, function(err, row) {
if (!row) return done(null, false);
return done(null, row);
});
});
app.post('/login', passport.authenticate('local', { successRedirect: '/good',
failureRedirect: '/bad' }));
// Configure view engine to render EJS templates.
app.set('views', __dirname + '/views');
app.set('view engine', 'ejs');
// Define routes.
app.get('/',
function(req, res) {
res.render('home', { user: req.user });
});
app.get('/login',
function(req, res){
res.render('login');
});
app.get('/logout',
function(req, res){
req.logout();
res.redirect('/');
});
app.get('/profile',
require('connect-ensure-login').ensureLoggedIn(),
function(req, res){
res.render('profile', { user: req.user });
});
app.get("/user", async (request, response) => {
const db = await database;
const results = await db.all("SELECT * FROM users");
response.send(results);
});
app.post("/user", async (request, response) => {
const db = await database;
hashedPassword = hashPassword(request.body.password, request.body.salt)
const created = await db.run(
"INSERT INTO users (username, password, salt) VALUES(?,?,?)",
request.body.username,
hashedPassword,
request.body.salt
);
const user = await db.get("SELECT * FROM users WHERE Id = ?", [
created.lastID
]);
response.status(201).send(user);
});
app.listen(3000);

I am grabbing my code from a project in production. Although it is not using SQLite, the concept should be the same.
I use bcrypt instead of implementing my own crypto. I suggest you do the same to avoid error and security issues in case of misapplication.
I also put the passport logic in a separate file to make the code looks cleaner and avoid confusion.
// Dependencies
const passport = require('passport');
const { Strategy: LocalStrategy, } = require('passport-local');
const bcrypt = require('bcrypt');
// Load model for User_DB
const { User_DB, } = require('../dataBase/dbConnection');
// Winston Logger
const passLog = require('../system/log').get('passportLog');
// Session
// Take in user id => keep the session data small
passport.serializeUser((id, done) => {
done(null, id);
});
// Deserialize when needed by querying the DB for full user details
passport.deserializeUser(async (id, done) => {
try {
const user = await User_DB.findById(id);
done(null, user);
} catch (err) {
passLog.error(`Error Deserializing User: ${id}: ${err}`);
}
});
// Export the passport module
module.exports = (passport) => {
passport.use(new LocalStrategy({ usernameField: 'email', }, async (email, password, done) => {
try {
// Lookup the user
const userData = await User_DB.findOne({ email: email, }, {
password: 1, }); // Return the password hash only instead of the whole user object
// If the user does not exist
if (!userData) {
return done(null, false);
}
// Hash the password and compare it to the hash in the database
const passMatch = await bcrypt.compare(password, userData.password);
// If the password hash does not match
if (!passMatch) {
return done(null, false);
}
// Otherwise return the user id
return done(null, userData.id);
} catch (err) {
passLog.error(`Login Error: ${err}`);
}
}));
};
These options for passport seems to malfunction a lot or exhibit weird behaviors, so I suggest you handle the redirection logic like in my controller.
{ successRedirect: '/good',
failureRedirect: '/bad' }
Login controller logic:
(I am omitting the code here for session storage and made some modifications, but this code should work for what you need)
const login = (req, res, next) => {
//Using passport-local
passport.authenticate('local', async (err, user) => {
//If user object does not exist => login failed
if (!user) { return res.redirect('/unauthorized'); }
//If all good, log the dude in
req.logIn(user, (err) => {
if (err) { return res.status(401).json({ msg: 'Login Error', }); }
// Send response to the frontend
return res.redirect('/good');
});
});
})(req, res, next);
};
The actual route:
// Import the controller
const {login} = require('../controllers/auth');
// Use it in the route
router.post('/auth/login', login);

Related

Unable to get Passport working with React & Node, without Passport authentication works fine

This is my first try with PassportJS, and I am following their documentation, which is very hard to understand for me.
My goal is to have a user register through Registration Page, a react component and then use the Login page, a react component as well, to login and redirect to a Dashboard, which is again a react component.
All the above works, but without Passport. I am not sure what I am doing wrong, as this is my first time working with it, but it seems to not working. And by not working I mean that I don't see any error message and the login page is not redirecting to dashboard.
Although I have managed to almost put things together, somehow either I am missing something or doing something wrong.
This is my React Login Component, which is sending Axios post request to API:
const onSubmit = (e) => {
e.preventDefault();
const newLogin = {
username,
password,
};
const config = {
headers: {
'Content-Type': 'application/JSON',
},
};
axios.post('/api/v1/login', newLogin, config).then((res) => {
return res.data === 'user'
? (window.location = '/dashboard')
: (window.location = '/login');
});
setUsername('');
setPassword('');
};
On Node server.js, the above login post request is sent to controller through router.
server.js:
app.use(passport.initialize());
app.use(passport.session());
app.use('/api/v1/register', register);
app.use('/api/v1/login', login);
Router.js (Login):
const { loginUser } = require('../controllers/loginController');
router.route('/').post(loginUser);
loginController.js:
require('../config/passport')(passport);
exports.loginUser = (req, res, next) => {
passport.authenticate('local', {
successRedirect: '/dashboard',
failureRedirect: '/register',
failureFlash: true,
});
};
This is my passport.js file:
const User = require('../models/UserModel');
const passport = require('passport');
module.exports = function (passport) {
passport.use(
new LocalStrategy((username, password, done) => {
// Match User
User.findOne({ username: username })
.then((err, user) => {
if (!user) {
return done(null, false, console.log('no user'));
}
//Match Password
bcrypt.compare(password, user.password, (err, isMatch) => {
if (err) throw err;
if (isMatch) {
return done(null, user);
} else {
return done(null, false, { message: 'Password incorrect' });
}
});
})
.catch((err) => console.log(err));
})
);
};
passport.serializeUser((user, done) => {
done(null, user.id);
});
passport.deserializeUser((id, done) => {
User.findOne(id, (err, user) => {
done(err, user);
});
});
I'd suggest removing the redirects from the passport login controller and handling the routing from the react app internally (via react router or otherwise) based on whether or not the app received a user in the response. Here's how I've got it set up in my recent project:
//router.js
const express = require("express");
const fs = require("fs");
const { userModel } = require("../models");
const passport = require("passport");
const bcrypt = require("bcrypt");
const bodyParser = express.urlencoded({ extended: true, limit: "50mb" });
const jsonParser = express.json({ limit: "50mb" });
const router = express.Router();
const saltRounds = 10;
router.get("/login", (req, res) => {
res.render("login");
});
router.get("/logout", (req, res, next) => {
req.logout();
req.session.destroy();
next();
});
router.post("/login", bodyParser, jsonParser, **passport.authenticate("local"),** (req, res, next) => {
if (!req.user) return;
const date = new Date();
console.log(`User ID:${req.user._id} logged in at ${date}`);
res.json(req.user);
next();
});
module.exports = router;
Note how I'm just using passport as middleware, then move on to the next function, which checks for the user it should have received from the previous function

How to call multiple passport-jwt strategies in the same app

I'm able to differentiate the two routes and
authenticate the first route(student). When i call the second route(teacher) to be authenticated, a empty JSON file is returned.
In users.js 'passportStudent.authenticate' works and returns a JSON file with user information as intended, But i'm not sure why 'passportTeacher.authenticate' returns a empty JSON file.
Here's My Code:
users.js:
const passportStudent = require('passport');
const passportTeacher = require('passport');
/*Some code here*/
router.get('/profile/student', passportStudent.authenticate('student', {session: false}), (req, res, next) =>{
res.json({user : req.user});
});
router.get('/profile/teacher', passportTeacher.authenticate('teacher', {session: false}), (req, res, next) =>{
res.json({teacher : req.teacher});
});
passport.js:
const JwtStrategy = require('passport-jwt').Strategy;
const ExtractJwt = require('passport-jwt').ExtractJwt;
const User = require('../models/user');
const Teacher = require('../models/teacher');
const config = require('../config/database');
module.exports = function(passport){
let opts = {};
opts.jwtFromRequest = ExtractJwt.fromAuthHeaderWithScheme('jwt');
opts.secretOrKey = config.secret;
passport.use('student', new JwtStrategy(opts, (jwt_payload, done) => {
console.log(jwt_payload);
User.getUserById(jwt_payload.data._id, (err, user) => {
if(err){
return done(err, false);
}
if(user){
return done(null, user);
}
else{
return done(null, false);
}
});
}));
passport.use('teacher', new JwtStrategy(opts, (jwt_payload2, done) => {
console.log(jwt_payload2);
Teacher.getTeacherById(jwt_payload2.data._id, (err, teacher) => {
if(err){
return done(err, false);
}
if(teacher){
return done(null, teacher);
}
else{
return done(null, false);
}
});
}));
}
app.js code for Passport:
const passportStudent = require('passport');
const passportTeacher = require('passport');
/*Some code here*/
app.use(passportStudent.initialize());
app.use(passportStudent.session());
app.use(passportTeacher.initialize());
app.use(passportTeacher.session());
require('./config/passport')(passportStudent);
require('./config/passport')(passportTeacher);
How can I authenticate either on desired routes? Or should I go for local strategy for student and jwt for teacher?
Thanks for your help.
In both cases, the passport store the signed users information in req.user.
So you need to update the teacher router to
router.get('/profile/teacher', passportTeacher.authenticate('teacher', {session: false}), (req, res, next) =>{
res.json({teacher : req.user});
});
Here I change
res.json({teacher : req.teacher});
To
res.json({teacher : req.user});

UnhandledPromiseRejectionWarning: Unhandled promise rejection in nodejs server

I am trying to use postman for the first time and yes i am a beginner with this authentication. I have a register page and a login page in angular and my backend is running on another port which is coded in nodejs. So i have installed passport and other required packages for the login authentication and registering user process. But on sending request to register route for registering a user i get the UnhandledPromiseRejectionWarning error. Even though there is then and catch inside the route it is still giving this error because of which i am confused. The data that i am sending is perfect but the error is occuring on the server side.
Here is my app.js file that contains all the routes
var express = require('express');
var mongoose = require('mongoose');
var bodyparser = require('body-parser');
var cors = require('cors');
var session = require('cookie-session');
var flash = require('connect-flash');
var passport = require('passport');
var bcrypt = require('bcryptjs');
require('./config/passport')(passport);
var User = require('./models/User');
var app = express();
app.use(bodyparser.json());
app.use(cors());
var expiryDate = new Date(Date.now() + 60 * 60 * 1000) // 1 hour
app.use(session({
name: 'session',
keys: ['key1', 'key2'],
cookie: {
secure: true,
httpOnly: true,
domain: 'example.com',
path: 'foo/bar',
expires: expiryDate
}
}))
app.set('port', process.env.port || 3000);
app.use(passport.initialize());
app.use(passport.session());
// Connect flash
app.use(flash());
// Global variables
app.use(function(req, res, next) {
res.locals.success_msg = req.flash('success_msg');
res.locals.error_msg = req.flash('error_msg');
res.locals.error = req.flash('error');
next();
});
var db = mongoose.connect("mongodb://localhost:27017/server", {
useNewUrlParser: true
}, function(err, response) {
if (err) {
console.log('There is error in connecting with mongodb');
}
console.log('Connection has been established.');
});
app.get('/', (req, res) => {
res.send("hello");
});
//Trying registering with passport
app.post('/register', (req, res) => {
console.log(req.body);
debugger;
const { firstname, lastname, email, password } = req.body;
let errors = [];
if (errors.length > 0) {
res.render('register', {
errors,
name,
email,
password,
password2
});
} else {
User.findOne({ email: email }).then(user => {
if (user) {
errors.push({ msg: 'Email already exists' });
res.render('register', {
errors,
firstname,
lastname,
email,
password
});
} else {
const newUser = new User({
firstname,
lastname,
email,
password
});
bcrypt.genSalt(10, (err, salt) => {
bcrypt.hash(newUser.password, salt, (err, hash) => {
if (err) throw err;
newUser.password = hash;
newUser
.save()
.then(user => {
req.flash(
'success_msg',
'You are now registered and can log in'
);
res.redirect('/login');
})
.catch(err => console.log(err));
});
});
}
});
}
});
//end
app.post('/login', (req, res, next) => {
console.log(req.body);
passport.authenticate('local', {
successRedirect: '/dashboard',
failureRedirect: '/login',
failureFlash: true
})(req, res, next);
});
app.listen(app.get('port'), function(err, response) {
console.log("Server is running");
});
passport.js file in config folder
const LocalStrategy = require('passport-local').Strategy;
const mongoose = require('mongoose');
const bcrypt = require('bcryptjs');
// Load User model
const User = require('../models/User');
module.exports = function(passport) {
passport.use(
new LocalStrategy({ emailField: 'email' }, (email, password, done) => {
// Match user
User.findOne({
email: email
}).then(user => {
if (!user) {
return done(null, false, { message: 'That email is not registered' });
}
// Match password
bcrypt.compare(password, user.password, (err, isMatch) => {
if (err) throw err;
if (isMatch) {
return done(null, user);
} else {
return done(null, false, { message: 'Password incorrect' });
}
});
});
})
);
passport.serializeUser(function(user, done) {
done(null, user.id);
});
passport.deserializeUser(function(id, done) {
User.findById(id, function(err, user) {
done(err, user);
});
});
};
To resolve this specific issue, you need to provide a catch() when you execute User.findOne() at the same level as the outermost then():
User.findOne({ email: email })
.then(user => { /* existing code */})
.catch(err => console.log(err)); // add catch()
And:
// Match user
User.findOne({ email: email })
.then(user => { /* existing code */ })
.catch(err => console.log(err)); // add catch()
Regarding the issue with the engine. You need to specify a rendering engine such as pug or html or ejs, this is something that Express as a framework expects. The link you provided that you are basing this code off of is using a rendering engine, specifically ejs. This is evident from the use of res.render(), which renders templates from the "views" folder.
app.set('view engine', 'ejs');
Even with the rendering engine, set you don't actually need to render any templates. As long as you remove res.render() and instead just use res.send() or equivalent, you can use this Express application as an API.
If your intention is to NOT use templates but still render the built angular files, you need to update the code and remove all instances of res.render() and res.redirect(). You can instead, still setting a view engine to satisfy Express, do something along the following:
// towards top of file
app.use(express.static(path.join(__dirname, 'path/to/built/angular')));
// ...
// after all other routes
app.get('*', (req, res) =>{
res.sendFile(path.join(__dirname+'/path/to/built/angular/index.html'));
});
Hopefully that helps!

JWT login issue

I'm using JWT with Passport for authentication in my app. I use MongoDB and Mongoose for database. I added a user with username = manager and password=manager with addManager function the password saved in db is not "manager" and is : "$2b$10$O7YAgTJKETOY.lanZoErWum2e6ZPpVi.RjMp0VHKfMT82z9uEmrlS" I don't know why! and I can't login with password :"manager". I don't know what is the problem .I used this article for my code. if you understand what't wrong please tell me.
app.js :
//settings and Requirements
const express = require('express');
const bodyParser = require('body-parser');
const cookieParser = require('cookie-parser');
const path = require('path');
const mongoose = require('mongoose');
const passport = require('passport');
const config = require('./api/config/database');
mongoose.connect(config.database);
let db = mongoose.connection;
require('./api/config/auth');
db.once('open', function () {
console.log('connected to mongodb...');
});
db.on('error', function (error) {
console.log(error);
});
let User = require('./api/models/user');
let basics = require('./api/config/basics');
let port = process.env.PORT || 8080;
const app = express();
app.use(bodyParser.urlencoded({
extended: false
}));
app.use(bodyParser.json());
app.use(bodyParser.json({
type: 'application/vnd/api+json'
}));
app.use(cookieParser());
basics.addManager(basics.manager);
router(app);
app.listen(port, function (err) {
if(err)
console.log(err);
console.log("port is " + port);
});
auth.js:
const passport = require('passport');
const LocalStrategy = require('passport-local').Strategy;
const User = require('../../api/models/user');
const passportJWT = require("passport-jwt");
const JWTStrategy = passportJWT.Strategy;
const ExtractJWT = passportJWT.ExtractJwt;
passport.use(new LocalStrategy({
usernameField: 'username',
passwordField: 'password'
},
function (username, password, cb) {
//this one is typically a DB call. Assume that the returned user object is pre-formatted and ready for storing in JWT
return User.findOne({username:username, password:password})
.then(user => {
if (!user) {
return cb(null, false, {message: 'Incorrect username or password.'});
}
return cb(null, user, {message: 'Logged In Successfully'});
})
.catch(err => cb(err));
}
));
passport.use(new JWTStrategy({
jwtFromRequest: ExtractJWT.fromAuthHeaderAsBearerToken(),
secretOrKey : 'your_jwt_secret'
},
function (jwtPayload, cb) {
//find the user in db if needed. This functionality may be omitted if you store everything you'll need in JWT payload.
return User.findOneById(jwtPayload.id)
.then(user => {
return cb(null, user);
})
.catch(err => {
return cb(err);
});
}
));
authenticate.js:
const express = require('express');
const passport = require('passport');
const jwt = require('jsonwebtoken');
const router = express.Router();
router.post('/login', function (req, res, next) {
console.log(req.body);
passport.authenticate('local', {session: false}, (err, user, info) => {
console.log(user);
if (err || !user) {
return res.status(400).json({
message: info ? info.message : 'Login failed',
user : user
});
}
req.login(user, {session: false}, (err) => {
if (err) {
res.send(err);
}
// generate a signed son web token with the contents of user object and return it in the response
const token = jwt.sign(user, 'your_jwt_secret');
return res.json({user, token});
});
})(req, res);
});
module.exports = router;
addManager:
`
addManager : function(manager){
let newUser = new User({
username : manager.username,
password : manager.password,
isManager : true
});
newUser.save(function(err , user){
if(err)
console.log(err);
console.log('manager added successfuly');
});
}
`

Passport.js Authentication within Express.js seems to be broken after implementing db connection pooling

Really confuse why my app login behaviour seems to be weird after i implemented pooling. Everything was fine when i use createConnection() instead of createPool() method inside my db configuration. The "weird" behaviour appears when i clear all of the session that stored in database( I use mysql store to impelement persistent session). The weird behavior is: It redirects back to login page even if i have filled the form correctly and submit it. When i try to refresh the page (pressing F5), it's finally redirect me to the correct page (home). The problem also appears in logout. When i press log out button, the session is destroyed, but it redirects me to home page instead of login. Everything was fine when i didn't use pooling.
Here's my auth controller:
index.js (controller)
var session = require('express-session');
var flash = require('connect-flash');
var passport = require('passport');
var bcrypt = require('bcrypt-nodejs');
var LocalStrategy = require('passport-local').Strategy;
//config
var pool = require('../config/db.js');
//serialize and deserialize passport
passport.serializeUser(function(user, done) {
console.log('user serialized');
done(null, user.username);
});
passport.deserializeUser(function(username, done) {
pool.query("SELECT * FROM user WHERE username = ?", [username], function(err, rows, fields) {
console.log('deserialized');
done(err, rows[0]);
});
});
passport.use(new LocalStrategy({
passReqToCallback: true
}, function(req, username, password, done) {
pool.query("SELECT * FROM user WHERE username = ?",[username], function(err, rows, fields) {
if (err) throw err;
if (!rows[0]) {
return done(null, false, req.flash('loginMessage', 'Oops! Wrong username.'));
}
if (!bcrypt.compareSync(password, rows[0].password)) {
return done(null, false, req.flash('loginMessage', 'Oops! Wrong password.'));
}
return done(null, rows[0]);
});
}));
module.exports.login_get = function(req, res, next) {
if(req.isAuthenticated()){
var sql = "SELECT * FROM venue";
pool.query(sql, function(err, rows, fields){
res.render('index/home', {person:req.user, venues:rows});
});
}
else{
res.render('index/login');
}
}
module.exports.login_post = passport.authenticate('local', {
successRedirect: '/',
failureRedirect: '/',
failureFlash: true
});
module.exports.logout_get = function(req, res) {
req.logout();
res.redirect('/');
};
index.js(routes)
var express = require('express');
var router = express.Router();
//load controller
var index = require('../controllers/index');
//load auth middleware
var auth = require('../controllers/auth_middleware');
//route level middleware
router.get('/', index.login_get);
router.post('/', index.login_post);
router.get('/logout', auth.isLoggedIn, index.logout_get);
module.exports = router;
db.js (configuration)
var mysql = require('mysql');
var dbConnectionInfo = {
host: 'localhost',
user: 'root',
password: '',
database: 'me',
connectionLimit: 10, //set connectionLimit for pooling
};
//create pool
var pool = mysql.createPool(dbConnectionInfo);
pool.on('connection', function (connection) {
console.log('DB Connection established');
connection.on('error', function (err) {
console.error(new Date(), 'MySQL error', err.code);
});
connection.on('close', function (err) {
console.error(new Date(), 'MySQL close', err);
});
});
module.exports = pool;
auth.middleware.js
module.exports.isLoggedIn = function(req, res, next){
if (req.isAuthenticated()) {
return next()
}
else{
res.redirect('/');
}
}
module.exports.isSuperAdmin = function(req, res, next){
if(req.user.role === "superadmin"){
return next();
}
else{
res.redirect('/');
}
}
I wrote console.log inside serialize and deserialize method. When i first log in user is serialized and NOT deserialized. When i refresh the page with f5, user is finally deserialized and i'm redirected to the home page. Why the deserialize mthod is not executed?

Resources