Qu'est-ce qu'un JWT
Fait partie de la section Authentification et sécurité du Journey Node.js de Coddy. Leçon 9 sur 17.
Une session nécessite une recherche dans la base de données à chaque requête. Un JWT (JSON Web Token) transporte lui-même les informations : trois parties base64url reliées par des points. L’en-tête indique l’algorithme, la charge utile contient les revendications (sub pour identifier à qui appartient le jeton, exp pour indiquer quand il expire), et la signature prouve que le serveur l’a créé.
Base64url est un encodage, pas un chiffrement. Toute personne qui détient un jeton peut lire sa charge utile :
const payload = token.split('.')[1];
JSON.parse(Buffer.from(payload, 'base64url').toString());
// { sub: '42', name: 'Lin', exp: 1767225600 }decodeJwt(token) de jose fait la même chose et ignore la signature. Ne place donc jamais de secret dans une charge utile, et ne prends jamais de décision à partir d’une revendication fournie par decodeJwt : n’importe qui peut écrire n’importe quelle revendication.
Défi
FacileGET /inspect?token=...renvoie les revendications du jeton au format JSON, ou 400{ "error": "Not a JWT" }.GET /adminrenvoie 403{ "error": "Admins only" }à chaque requête.
Envoie d’abord le jeton de Mallory à GET /admin?token= et vois ce qu’il ouvre : eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJtYWxsb3J5IiwiYWRtaW4iOnRydWV9.c2lnbmF0dXJl. Corrige-le, puis envoie le même jeton à /inspect.
Essayez vous-même
import express from 'express';
import { decodeJwt } from 'jose';
import { users } from './db.js';
import { hashPassword, verifyPassword } from './passwords.js';
const app = express();
app.use(express.json());
app.post('/signup', async (req, res) => {
const username = String(req.body.username ?? '').trim().toLowerCase();
const password = String(req.body.password ?? '');
if (!username || password.length < 8) {
return res.status(400).json({ error: 'Pick a username and a password of at least 8 characters' });
}
if (await users.findOne({ username })) {
return res.status(409).json({ error: 'That username is taken' });
}
await users.insertOne({ username, passwordHash: await hashPassword(password) });
res.status(201).json({ username });
});
app.post('/login', async (req, res) => {
const username = String(req.body.username ?? '').trim().toLowerCase();
const user = await users.findOne({ username });
if (!user || !(await verifyPassword(String(req.body.password ?? ''), user.passwordHash))) {
return res.status(401).json({ error: 'Wrong username or password' });
}
res.json({ username: user.username });
});
// Affiche le contenu d’un token : GET /inspect?token=...
app.get('/inspect', (req, res) => {
res.status(501).json({ error: 'Not written yet' });
});
// La page d’administration.
app.get('/admin', (req, res) => {
const claims = decodeJwt(String(req.query.token ?? ''));
if (claims.admin === true) {
return res.json({ secret: 'The vault key is 42' });
}
res.status(403).json({ error: 'Admins only' });
});
app.listen(3000, () => {
console.log('Vault is running on port 3000');
});
Cette leçon comprend un petit quiz. Commencez la leçon pour y répondre et suivre votre progression.