Tuesday | 05 NOV 2024
[ previous ]
[ next ]

Setting up Authentication in Node

Title:
Date: 2022-01-10
Tags:  

The simple steps to set up your own authentication in node.

One requirement of this is using the dotenv package and .env file with all the database settings. You can replace the process.env with hardcoded variables if you aren't using environment variables.

The first thing we need to do is install sessions and a session backend. The session backend I like to use is postgres.

> npm install express-session
> npm install connect-pg-simple

Express sessions sets up the session logic while connect-pg-simple is the storage we will be using.

Once we have our session backend installed, we need to create a session table in postgres.

> sudo /usr/pgsql-13/bin/psql -h localhost -d somedb -U username < node_modules/connect-pg-simple/table.sql

This command creates a session table in the database with 3 columns, sid which is the session id, sess which is a json object of all the data you save for each session and finally an expire column which is the expiry. This command also creates an index on the expiry column.

Now we are ready to add sessions to the application.

Inside app.js, add the following:

var app = express();

var session = require('express-session');
var pgSession = require('connect-pg-simple')(session);

const oneDay = 1000 * 60 * 60 * 24;

var cookie = { maxAge: oneDay };
var secure_cookie = { secure: true, httpOnly: true, sameSite: 'none', maxAge: oneDay };

if (process.env.ENV === "production") {
    cookie = secure_cookie;
}

const pg = {       
    user: process.env.DB_USERNAME,
    password: process.env.DB_PASSWORD,
    host: process.env.DB_HOST,
    port: process.env.DB_PORT,
    database: process.env.DATABASE
}

const pgConnectionString = "postgres://${pg.user}:${pg.password}@${pg.host}:${pg.port}/${pg.database}";

app.use(session({
    proxy: process.env.ENV === 'production',
    store: new pgSession({
        conString: pgConnectionString
    }),
    secret: process.env.SECRET,
    saveUninitialized: false,
    cookie: cookie,
    resave: true,
}));
  • replace the double quotes with the template string quote.

We start by importing the modules we need, session and pgSession. Next we set up our cookie, depending on dev vs production, we'll be using different cookies.Next we create our connection string to the postgres database.

Finally we add the session middleware to our app. The middleware needs to know what session backend we're going to use, and the cookie options it will be using.

The proxy flag allows node to trust cookies that come through a reverse proxy. The reason I gate this for live is because our cookie isn't secure in development, only secure cookies need the proxy flag.

The store is set to the postgres backend.

The secret in the session is the key used to encrypt and decrypt sessions(sign? Not sure if this is just hashing key rather than an encryption thing). If we change the secret, then all the sessions currently being used would become invalid.

The saveUninitialized flag lets the application save sessions for users that don't log in, however I don't want to add that clutter so I set the flag to false.

The cookie is the cookie options. Secure means that the cookie is only sent over SSL. HttpOnly means that the cookie cannot be access by third party scripts. Samesite being set to none means that all requests will be made with cookies. Maxage sets the lifetime of the cookie.

The resave in a session will allow for cookies to be renewed each time the cookie is used. This way it can be technically forever if the user logs in once a day.

With that, the application is set up for sessions. Now we need to log in users and then log them out.

router.post('/login', async function(req, res, next) {
    try {
        var username = req.body.username;
        var user = await db.User.findOne({ where: { username: username }}); 

        if(user === null) {
            res.send("Password incorrect.");
            return;
        }

        var valid = await bcrypt.compare(req.body.password, user.password);
        if (!valid) {
            res.send("Incorrect password.");
            return;
        }

        req.session.user = {
            id: user.id,
        }

        res.redirect("/");
    } catch (e) { next(e); }
});

The login function is simple, we go to the database to find the user and then check their password. If everything is valid, we create a user object on the session and set whatever we want on the user object. You can create any object on the session, I just name it user so its straightforward to pass it along in my templates.

The logout function is even simpler.

router.post('/logout', function(req, res, next) {
    req.session.destroy();
    return res.redirect("/");
});

I did actually forget the middleware that checks for sessions!

This would be in a valdiateAccess file.

module.exports = (req, res, next) => req.session && req.session.user
    ? next()
    : res.redirect("/login");

This method simply checks to see if the session exists and if the user object on the session exists.

We can then pass in the valdiateAccess function as middleware.

router.get('/user/123', validateAccess, csrfProtection, async (req, res, next) => {
});

With that, authentication is done!