Search code examples
node.jsexpressauthenticationpassport.jspassport-local

Passport: Authenticating the whole app instead of some requests


I am new to web development and NodeJS. I am working on authentication using passport. It was a small app so I put check on each route request to check whether the user is authenticated or not; But I guess that technique won't be feasible for a big app. I want to authenticate the whole app. I know that it has something to do with middleware as each request passes through middleware, but I can't figure out where. Any middleware related explanation would be appreciated.

Here is my code

const express = require('express');
const app = express();
const path = require('path');
const mongo = require('mongodb').MongoClient;
const cookieParser = require('cookie-parser');
const expressHandlebars = require('express-handlebars');
const expressValidator = require('express-validator');
const session = require('express-session');
const passport = require('passport');
const localStrategy = require('passport-local').Strategy;
const mongoose = require('mongoose');
const User = require('./models/user');
const Admin = require('./models/admin');


app.engine('handlebars', expressHandlebars({defaultLayout:'layout'}));
app.set('view engine', 'handlebars');

const port = 8888;
const bodyParser = require('body-parser');
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(cookieParser());

app.use(session({
    secret: 'secret',
    saveUninitialized: true,
    resave: true
}));

app.use(passport.initialize());
app.use(passport.session());

//express Validator
app.use(expressValidator({
    errorFormatter: function(param, msg, value) {
        var namespace = param.split('.'),
            root = namespace.shift(),
            formParam = root;
        while (namespace.length) {
            formParam += '[' + namespace.shift() + ']';
        }
        return {
            param: formParam,
            msg: msg,
            value: value
        };
    }
}));

app.use(express.static(__dirname));

mongoose.Promise = global.Promise;
const url = 'mongodb://localhost:27017/userDB';
mongoose.connect(url);

Here is my login functionality.

app.get("/login", function (req, res) {
    const loginPath = path.join(__dirname, '/login.html');
    res.sendFile(loginPath);
});

passport.use(new localStrategy({
        usernameField: 'adminUsername',
        passwordField: 'password',
        session: false
    },
    function (adminUsername, password, done) {
        Admin.getAdminByAdminUsername(adminUsername, function (err, admin) {
            if (err) throw err;
            console.log('getAdmin called');
            if (!admin) {
                console.log('Admin Not Found');
                return done(null, false);
            }

            Admin.comparePassword(password, admin.password, function (err, isMatch) {
                console.log('comparePassword called');
                if (err) throw err;
                if (isMatch) {
                    return done(null, admin);
                } else {
                    console.log('Wrong Password!');
                    return done(null, false);
                }
            });
        });
    }));

passport.serializeUser(function (admin, done) {
    done(null, admin.id);
});
passport.deserializeUser(function (id, done) {
    Admin.getAdminById(id, function (err, admin) {
        done(err, admin);
        console.log('findById called');
    });
});

app.post('/login', passport.authenticate('local', {
        failureRedirect: '/login'}), function(req, res){
        console.log('login called');
        res.redirect('/');
    });

function ensureAuthenticated(req, res, next){
    console.log(req.isAuthenticated());
    if (req.isAuthenticated()) {
        return next();
    } else {
        res.redirect('/login');
    }
}

Previously, this is how I put a check on each request. Here is an example.

app.get("/update", ensureAuthenticated, function (req, res) {
    const updatePath = path.join(__dirname, '/update.html');
    res.sendFile(updatePath);
});

Solution

  • ensureAuthenticated is a middleware that you can also use standalone, like this:

    app.use(ensureAuthenticated);
    

    Express will pass requests to middleware and route handlers in order of their declaration. That means that if you add the line above in front of other middleware and route handlers, it will always be called, for each request (but read below why you might not actually want that). That way, you don't have to add it explicitly to each request handler.

    Typically though, you separate "requests that require authentication" from requests that don't. For instance, requests for static resources (client-side JS, CSS, HTML, etc) typically don't require authentication. That means that you need to declare the static handler before ensureAuthenticated:

    app.use(express.static(__dirname));
    app.use(ensureAuthenticated);
    

    The same goes for requests that are part of the login process: the login page and login request handler, for the simple reason that requiring a user to be logged in before they can access the login page doesn't make sense.

    So the overall structure of your middleware/route handlers would look like this:

    • Common middleware (body parser, cookie handler, passport middleware, etc)
    • app.use(express.static(...))
    • Login routes
    • app.use(ensureAuthenticated)
    • "Protected" routes