Signer des jetons
Fait partie de la section Authentification et sécurité du Journey Node.js de Coddy. Leçon 10 sur 17.
Un jeton n’a de valeur que si personne d’autre ne peut en créer un. jose le signe avec HS256 : un HMAC de l’en-tête et de la charge utile, calculé à l’aide d’un secret que seul le serveur connaît.
Le secret provient d’une variable d’environnement, process.env.JWT_SECRET, et jamais du code : le code est partagé, copié et validé dans le dépôt. Cette leçon définit JWT_SECRET pour toi. jose en a besoin sous forme d’octets.
const key = new TextEncoder().encode(process.env.INVITE_SECRET);
const invite = await new SignJWT({ room: 'design' })
.setProtectedHeader({ alg: 'HS256' })
.setSubject(guestId)
.setIssuedAt()
.setExpirationTime('2d')
.sign(key);setSubject prend du texte, et setExpirationTime prend une durée comme '30m' ou '2d'.
Défi
FacilePOST /tokenavec{ username, password }renvoie{ token }: HS256, signé avecJWT_SECRET, avec la revendicationusername, l’identifiant de l’utilisateur comme sujet sous forme de texte, et une expiration au bout d’une heure.- Une tentative de connexion incorrecte renvoie 401
{ "error": "Wrong username or password" }.
Inscris-toi, obtiens un jeton et colle-le dans GET /inspect?token=.
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) => {
try {
res.json(decodeJwt(String(req.query.token ?? '')));
} catch {
res.status(400).json({ error: 'Not a JWT' });
}
});
// La page d’administration. Aucune déclaration ne peut être considérée comme fiable avant la vérification de sa signature.
app.get('/admin', (req, res) => {
res.status(403).json({ error: 'Admins only' });
});
app.listen(3000, () => {
console.log('Vault is running on port 3000');
});