Nel corso delle lezioni precedenti abbiamo acquisito una solida comprensione dei fondamenti di OAuth 2.0 e dei JSON Web Tokens (JWT). In questa lezione, ci concentreremo sull'implementazione pratica di JWT per l'autenticazione e la protezione delle API. Vedremo come generare, convalidare e utilizzare i JWT in un'architettura basata su API, affrontando anche le migliori pratiche di sicurezza.
Cos'è un JSON Web Token (JWT)?
Come abbiamo visto nella lezione precedente, un JSON Web Token è uno standard aperto (RFC 7519) che permette la trasmissione sicura di informazioni tra le parti come oggetti JSON firmati. I JWT vengono spesso utilizzati per l'autenticazione stateless, eliminando la necessità di mantenere sessioni sul server. Essi sono composti in breve da tre parti:
-
Header
-
Payload
-
Signature
Un JWT ha poi il seguente formato:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoxMjMsInJvbGUiOiJhZG1pbiIsImV4cCI6MTY5MDg3NTYwMH0.wzYzx-MEFIXYVQblW9pRFLUpDyl3CcrAvfKkQtIvxQw
Generazione di un JWT
Per generare un JWT, possiamo utilizzare una libreria come jsonwebtoken
in Node.js o pyjwt
in Python. Supponiamo di avere un backend in Node.js con Express. Possiamo generare un JWT come segue:
const jwt = require('jsonwebtoken');
const secretKey = "supersegreta123";
function generateToken(user) {
const payload = {
userId: user.id,
role: user.role
};
return jwt.sign(payload, secretKey, { expiresIn: '1h' });
}
const user = { id: 123, role: 'admin' };
const token = generateToken(user);
console.log("JWT Generato:", token);
Questo JWT conterrà l'ID utente e il ruolo, con una scadenza di un'ora.
Analogamente, in un ambiente PHP possiamo utilizzare la libreria firebase/php-jwt
per generare un JWT:
require 'vendor/autoload.php';
use Firebase\JWT\JWT;
$secretKey = "supersegreta123";
function generateToken($user) {
$payload = [
"userId" => $user["id"],
"role" => $user["role"],
"exp" => time() + 3600 // Scadenza tra un'ora
];
return JWT::encode($payload, $secretKey, 'HS256');
}
$user = ["id" => 123, "role" => "admin"];
$token = generateToken($user);
echo "JWT Generato: " . $token;
Validazione di un JWT
Una volta generato un JWT, dobbiamo verificare la sua validità quando viene utilizzato per accedere a un'API protetta. Supponiamo che l'utente invii il token nell'Authorization Header
della richiesta HTTP:
In Node.js:
const express = require('express');
const app = express();
function authenticateToken(req, res, next) {
const token = req.header('Authorization')?.split(' ')[1];
if (!token) return res.status(401).json({ message: "Accesso negato" });
jwt.verify(token, secretKey, (err, user) => {
if (err) return res.status(403).json({ message: "Token non valido" });
req.user = user;
next();
});
}
app.get('/api/protetta', authenticateToken, (req, res) => {
res.json({ message: "Accesso consentito", user: req.user });
});
app.listen(3000, () => console.log('Server avviato su porta 3000'));
authenticateToken
Se utilizziamo PHP, possiamo validare un JWT come segue:
use Firebase\JWT\JWT;
use Firebase\JWT\Key;
$secretKey = "supersegreta123";
function validateToken($token) {
try {
$decoded = JWT::decode($token, new Key($secretKey, 'HS256'));
return $decoded;
} catch (Exception $e) {
return null;
}
}
$token = "inserire_il_token_qui";
$decoded = validateToken($token);
if ($decoded) {
echo "Accesso consentito: ";
print_r($decoded);
} else {
echo "Token non valido.";
}
In breve, questo codice in PHP utilizza la libreria Firebase JWT
validateToken
null
$token
Tuttavia, il codice presenta alcune criticità, come l'uso di una chiave segreta poco sicura e la mancata gestione dettagliata degli errori, che potrebbero compromettere la sicurezza dell'applicazione.
Utilizzo di JWT per la protezione delle API
Le API RESTful moderne utilizzano JWT per l'autenticazione e l'autorizzazione. Ecco un flusso tipico:
-
l'utente si autentica con nome utente e password.
-
Il server verifica le credenziali e genera un JWT.
-
Il client memorizza il token (solitamente in
localStorage
sessionStorage
-
Ad ogni richiesta protetta, il client lo invia nell'
Authorization Header
-
Il server verifica il JWT e concede o nega l'accesso.
Questo metodo elimina la necessità di mantenere sessioni sul server, migliorando scalabilità e sicurezza.
Protezione e best practies nell'uso di JWT
Anche se i JSON Web Tokens offrono numerosi vantaggi, è fondamentale adottare le migliori pratiche di sicurezza:
Utilizzare una chiave segreta forte
La chiave segreta utilizzata per firmare i token deve essere robusta e sicura. Evitiamo stringhe deboli come password123
e utilizziamo strumenti come openssl
per generare una chiave forte:
openssl rand -base64 32
Impostare una scadenza adeguata
Evitare che un token sia valido per troppo tempo riduce i rischi in caso di furto del token stesso. Usiamo durate brevi (es. 15 minuti) e implementiamo un meccanismo di refresh token.
Utilizzare HTTPS
I JWT devono essere sempre trasmessi su HTTPS per prevenire attacchi di tipo Man-in-the-Middle (MITM).
Evitare di memorizzare JWT nei cookie non sicuri
Se si usa un cookie per memorizzare il token, assicuriamoci di configurarlo come HttpOnly
e Secure
:
res.cookie("token", jwtToken, { httpOnly: true, secure: true });
Implementare un sistema di revoca
Poiché i JSON Web Tokens sono stateless, il server non ha modo di invalidarli prima della loro scadenza. Un'opzione è mantenere una blacklist di token revocati.
Preferire RS256 invece di HS256
Quando possibile, usiamo algoritmi a chiave pubblica/privata (es. RS256) invece di chiavi simmetriche (HS256) per migliorare la sicurezza.
Conclusioni
Abbiamo visto come implementare JSON Web Tokens per autenticare e proteggere API RESTful. Abbiamo coperto la generazione, validazione e le best practices di sicurezza. Implementando correttamente i JWT, possiamo costruire applicazioni sicure e scalabili. Nella prossima lezione approfondiremo ulteriori strategie di sicurezza e best practices in OAuth 2.0 e JWT.