In questo articolo vedremo come implementare un sistema di login in ExpressJS con MongoDB salvando i dati dell'utente nella sessione.

Per MongoDB utilizzeremo il driver ufficiale e per salvare i dati nella sessione corrente ci serviremo del modulo cookie-session.

Questo modulo viene usato come middleware ed aggiunge un oggetto session all'oggetto request di ogni route. Aggiungendo ed eliminando proprietà a/da l'oggetto session facciamo in modo che i dati siano persistenti e disponibili in tutta l'applicazione. La sessione usa i cookie per gestire i suoi dati.

Configuriamo la nostra applicazione in questo modo:

'use strict';

const path = require('path');
const express = require('express');
const app = express();
const port = process.env.PORT || 3000;
const bodyParser = require('body-parser');
const helmet = require('helmet');
const cookieSession = require('cookie-session');
const { sessionName, sessionKeys } = require('./config');
const routes = require('./routes');


app.disable('x-powered-by');
app.set('view engine', 'ejs');


app.use('/public', express.static(path.join(__dirname, 'public')));
app.use(helmet());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(cookieSession({
    name: sessionName,
    keys: sessionKeys
}));
app.use('/', routes);



app.listen(port);

Il file di configurazione, config.js, contiene i dati per impostare la connessione al database e la sessione.

'use strict';

module.exports = {
    dbUrl: 'mongodb://localhost:27017',
    dbName: 'database',
    dbCollection: 'users',
    sessionName: 'session-name',
    sessionKeys: ['key-1', 'key-2']
};

Poiché le password degli utenti vengono cifrate con una funzione di hash, abbiamo bisogno di un metodo helper per tale scopo. Dobbiamo anche creare un metodo per la connessione al database. Definiamo questi due metodi nel modulo utils/index.js.

'use strict';

const crypto = require('crypto');
const mongo = require('mongodb').MongoClient;

module.exports = {
    hashString(str, type = 'md5' ) {
        return crypto.createHash(type).update(str).digest('hex');
    },
    async db(url) {
        try {
           const client = await mongo.connect(url, {
            useNewUrlParser: true,
            useUnifiedTopology: true
          });
          return client;
        } catch(err) {
            return err;
        }
    }
};

A questo punto dobbiamo definire le tre route di base della nostra applicazione. La prima visualizzerà la home page che cambierà dinamicamente se l'oggetto utente è presente nella sessione.

'use strict';

const express = require('express');
const router = express.Router();
const {db, hashString} = require('../utils');
const { dbUrl, dbName, dbCollection } = require('../config');

router.get('/', (req, res, next) => {
    res.render('index', { user:  req.session.user ? req.session.user : null } );
});

module.exports = router;

La view sarà la seguente:

<%- include('header') %>

<% if(user) { %>

<section id="main" class="text-center">
    <h1>Welcome, <%= user.email %></h1>
    <p class="mt-5">
        <a href="/logout" class="btn btn-primary">Logout</a>
    </p>
</section>

<% } else { %>

<form action="" id="login-form" method="post" novalidate>
    <div class="form-group">
        <input type="email" name="email" id="email" class="form-control" placeholder="Email">
    </div>
    <div class="form-group">
        <input type="password" name="password" id="password" class="form-control" placeholder="Password">
    </div>
    <p>
        <input type="submit" class="btn btn-primary btn-block" value="Login">
    </p>
</form>

<% } %>


<%- include('footer') %>

La seconda route gestirà il logout che consiste semplicemente nell'eliminare il riferimento all'utente nella sessione e reindirizzare il client sulla home page.

router.get('/logout', (req, res, next) => {
    if(req.session.user) {
        delete req.session.user;
        res.redirect('/');
    } else {
        res.sendStatus(403);
    }
});

La terza route è quella che gestisce il processo di login. È concepita per restituire un output in JSON in modo che il login possa essere implementato con AJAX lato client.

Se nel database è presente un riferimento all'utente identificato con e-mail e password (quest'ultima crittografata in MD5), viene salvato tale riferimento nella sessione. In caso contrario viene restituito un messaggio di errore.

router.post('/login', async (req, res, next) => {
    const { email, password } = req.body;
    try {
        const client = await db(dbUrl);
        const database = client.db(dbName);
        const users = database.collection(dbCollection);
        const existingUser = await users.findOne({email: email, password: hashString(password)});
        if(existingUser) {
            req.session.user = existingUser;
            res.json({ success: true });
        } else {
            res.json({ error: 'Invalid login.' }); 
        }

    } catch(err) {
        res.json({ error: 'Connection error.' });
    }
});

Come si può notare, l'intero processo è estremamente lineare e di facile implementazione.

Codice sorgente

GitHub