Quando si sviluppano applicazioni web, gestire in modo sicuro l'autenticazione degli utenti è essenziale. In questo contesto, JWT (JSON Web Token) è una delle soluzioni più moderne ed efficaci per l'autenticazione stateless, cioè senza sessioni salvate sul server.
In questa guida vedremo come costruire un sistema di login sicuro in Node.js usando Express, MongoDB per memorizzare gli utenti e JWT per gestire l'autenticazione.
Cosa useremo
- Node.js e Express per il server.
- MongoDB e Mongoose per gestire gli utenti.
- bcrypt per criptare le password.
- jsonwebtoken per firmare e verificare i token JWT.
- dotenv per gestire le variabili d’ambiente.
Setup del progetto
Creiamo il progetto e installiamo i pacchetti:
mkdir jwt-auth-app
cd jwt-auth-app
npm init -y
npm install express mongoose bcrypt jsonwebtoken dotenv
Creiamo anche il file .env:
PORT=3000
MONGO_URI=mongodb://localhost:27017/jwt_auth_db
JWT_SECRET=supersegreto123
Struttura del progetto
jwt-auth-app/
├── models/
│ └── User.js
├── routes/
│ └── auth.js
├── middleware/
│ └── auth.js
├── .env
├── server.js
└── package.json
Configuriamo il server su server.js:
const express = require('express');
const mongoose = require('mongoose');
const dotenv = require('dotenv');
dotenv.config();
const authRoutes = require('./routes/auth');
const app = express();
app.use(express.json());
mongoose.connect(process.env.MONGO_URI)
.then(() => console.log("Connesso a MongoDB"))
.catch(err => console.error(err));
app.use('/api/auth', authRoutes);
app.listen(process.env.PORT, () => {
console.log(` Server avviato su http://localhost:${process.env.PORT}`);
});
Creiamo poi il modello utente con models/User.js:
const mongoose = require('mongoose');
const userSchema = new mongoose.Schema({
email: { type: String, required: true, unique: true },
password: { type: String, required: true }
});
module.exports = mongoose.model('User', userSchema);
Rotte di autenticazione con routes/auth.js
const express = require('express');
const bcrypt = require('bcrypt');
const jwt = require('jsonwebtoken');
const User = require('../models/User');
const router = express.Router();
// REGISTRAZIONE
router.post('/register', async (req, res) => {
const { email, password } = req.body;
try {
const userExist = await User.findOne({ email });
if (userExist) return res.status(400).json({ message: "Utente già esistente" });
const hashedPassword = await bcrypt.hash(password, 10);
const newUser = new User({ email, password: hashedPassword });
await newUser.save();
res.status(201).json({ message: "Registrazione completata" });
} catch (err) {
res.status(500).json({ message: "Errore nel server" });
}
});
// LOGIN
router.post('/login', async (req, res) => {
const { email, password } = req.body;
try {
const user = await User.findOne({ email });
if (!user) return res.status(404).json({ message: "Utente non trovato" });
const match = await bcrypt.compare(password, user.password);
if (!match) return res.status(401).json({ message: "Password errata" });
const token = jwt.sign({ id: user._id }, process.env.JWT_SECRET, { expiresIn: '1h' });
res.json({ token });
} catch (err) {
res.status(500).json({ message: "Errore nel server" });
}
});
module.exports = router;
Middleware di protezione middleware/auth.js
Questo middleware serve per proteggere le rotte private:
const jwt = require('jsonwebtoken');
function authenticateToken(req, res, next) {
const authHeader = req.headers['authorization'];
const token = authHeader && authHeader.split(' ')[1]; // Bearer
if (!token) return res.status(401).json({ message: 'Accesso negato' });
jwt.verify(token, process.env.JWT_SECRET, (err, user) => {
if (err) return res.status(403).json({ message: 'Token non valido' });
req.user = user;
next();
});
}
module.exports = authenticateToken;
Rotta protetta (esempio)
Nel tuo routes/auth.js, puoi aggiungere una rotta privata:
const authenticateToken = require('../middleware/auth');
router.get('/profile', authenticateToken, async (req, res) => {
const user = await User.findById(req.user.id).select('-password');
res.json(user);
});
Con questa rotta, solo chi ha un token valido può accedere alle informazioni dell'utente.
Come funziona tutto insieme?
- L'utente si registra inviando email e password.
- La password viene criptata con bcrypt.
- Quando fa il login, la password viene confrontata.
- Se tutto è corretto, il server genera un token JWT.
- L'utente usa quel token per accedere a rotte protette.
- Il token viene verificato ad ogni richiesta per garantire la sicurezza.
Test con Postman o frontend
Puoi testare tutto con Postman:
- POST /api/auth/register
Body (JSON):{ "email": "test@email.com", "password": "123456" }- POST /api/auth/login
- GET /api/auth/profile
Headers → Authorization: Bearer
Vantaggi dell’uso di JWT
- Stateless: nessuna sessione salvata lato server.
- Scalabile: adatto ad architetture moderne come microservizi.
- Sicuro: i token sono firmati e, se configurati bene, difficili da falsificare.
- Versatile: possono contenere payload personalizzati (es. ruolo, ID, email, ecc.).
Estensioni e miglioramenti
Se vuoi espandere questo sistema puoi:
- Aggiungere il refresh token per una sicurezza maggiore.
- Impostare ruoli e permessi nel JWT.
- Utilizzare HTTPS per proteggere i token in transito.
- Salvare il token in cookie HttpOnly per protezione XSS.
- Collegare a un frontend (React, Vue, ecc.).
Conclusione
L'autenticazione basata su JWT (JSON Web Token) rappresenta oggi uno degli approcci più adottati nello sviluppo di applicazioni moderne, specialmente in ambito RESTful e SPA (Single Page Application). In questa guida abbiamo visto come implementare passo dopo passo un sistema di login e protezione delle rotte, usando Node.js, Express, MongoDB e una combinazione di strumenti come bcrypt e jsonwebtoken per garantire la sicurezza dei dati e la facilità di utilizzo.
La semplicità con cui i JWT possono essere generati, inviati e verificati li rende estremamente adatti per architetture distribuite e scalabili, dove più servizi o client devono comunicare in modo sicuro senza dover fare affidamento su sessioni lato server. L'approccio stateless favorisce anche la scalabilità verticale e orizzontale dell'applicazione, rendendola perfetta per ambienti in cloud o microservizi.
Con la protezione delle rotte tramite middleware, hai anche imparato come creare zone sicure all'interno dell'applicazione, accessibili solo da utenti autenticati. E grazie all'uso di Mongoose e MongoDB, hai visto come creare e gestire un modello utente semplice ma efficace.
Ovviamente, quanto mostrato in questa guida rappresenta una base solida, ma è solo l'inizio. In progetti reali è importante considerare ulteriori accorgimenti per rafforzare la sicurezza, come ad esempio:
- Gestione dei refresh token per sessioni più durature e sicure.
- Controllo degli accessi basato su ruoli (RBAC).
- Rate limiting e protezione da brute force durante il login.
- Salvataggio dei token in cookie HttpOnly per evitare attacchi XSS.
- Logging e tracciamento delle attività dell’utente per motivi di auditing.
- Implementazione di sistemi di reset password e verifica email.
Integrare JWT con frontend moderni come React, Vue o Angular è molto semplice, e ti permetterà di costruire esperienze utente fluide, reattive e sicure.
In definitiva, padroneggiare l'autenticazione con JWT in un ambiente Node.js è una competenza fondamentale per qualsiasi sviluppatore backend. Ti apre le porte a moltissimi progetti reali, professionali e commerciali. Con le conoscenze di oggi sei in grado di costruire un sistema di autenticazione robusto, personalizzabile e in linea con le best practices del web moderno.