0

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);
  • Seems like that there are some problems with the serialization. What's the output if you console log `row` in the `passport.deserializeUser` function? – Algo7 Jan 11 '21 at 16:50
  • When I add this to the code, I don't get any output in the console. – Michel Runzel Jan 11 '21 at 18:08
  • The function will only be triggered when the login process is carried out. So try to log in and see if the console says anything. – Algo7 Jan 11 '21 at 18:13
  • I tried this, but the only output is that the /bad site can't be found (I didn't created this page now). bad:1 GET http://localhost:3000/bad 404 (Not Found) – Michel Runzel Jan 11 '21 at 18:28
  • I just want to figure out if the serialization function is being called at all. It is being called, then it might be the problem with the routes. So when you get redirected, were the serialization functions executed'? – Algo7 Jan 11 '21 at 18:45
  • I don't think that the serialization function get's called. I added console.log('Hello') to the first line of the function and if I want to login I don't get the hello in the console – Michel Runzel Jan 11 '21 at 19:39
  • Then there are problems with the routes. How are you calling them, if you dont have a front end page. Are you testing it with Postman? – Algo7 Jan 11 '21 at 19:44
  • I have a frontend page and im calling the post /login method:

    – Michel Runzel Jan 11 '21 at 20:26

1 Answers1

0

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);
Algo7
  • 2,122
  • 1
  • 8
  • 19