Signer des jetons
Fait partie de la section Authentification et sécurité du Journey Node.js de Coddy. Leçon 15 sur 24.
Un jeton n’a de valeur que si personne d’autre ne peut en créer un. Avec HS256, jose combine l’en-tête et la charge utile avec un secret que seul le serveur connaît, puis hache le résultat. Ce hachage est la signature. Sans le secret, personne ne peut en créer une correspondante.
Le secret provient d’une variable d’environnement, process.env.JWT_SECRET, jamais du code : le code est partagé, copié et envoyé 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
FacileLes applications qui appellent votre API, comme une application mobile, ne conservent pas les cookies comme le fait un navigateur. Donnez-leur plutôt un jeton : un JWT signé qu’elles renvoient avec chaque requête. POST /token sert de connexion pour ces applications.
- Dans
server.js, importezSignJWTdepuisjose, à côté dedecodeJwt. Au-dessus des routes, convertissezprocess.env.JWT_SECRETen octets avec unTextEncoder, comme l’indique la leçon, carjosesigne avec des octets. Cette leçon définitJWT_SECRETpour vous. - Ajoutez
POST /token. Il reçoit le même corps que la route de connexion,{"username": "ada", "password": "correct horse"}, et vérifie le nom d’utilisateur et le mot de passe de la même manière quePOST /login. S’ils sont incorrects, renvoyez le statut 401 et{"error": "Wrong username or password"}. - S’ils sont corrects, signez un jeton et renvoyez-le sous la forme
{"token": "..."}. Le jeton doit :- utiliser l’algorithme HS256, signé avec les octets de
JWT_SECRET; - contenir la revendication
usernamedans sa charge utile ; - avoir l’
_idde l’utilisateur sous forme de texte comme sujet. UtiliseztoHexString(), carjoserefuse un sujet qui n’est pas du texte ; - expirer une heure après sa création.
Un jeton de cette leçon ressemble à ceci :
eyJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6ImFkYSIsInN1YiI6IjY2ZjFjMmE5ZTRiMGExYjJjM2Q0ZTVmNiIsImlhdCI6MTc5MzM2MTYwMCwiZXhwIjoxNzkzMzY1MjAwfQ.EdCrjAKSjZowcjE3-IjKttBLVK1VKKbL9DPnJDLdgug - utiliser l’algorithme HS256, signé avec les octets de
Essayez : Appuyez sur « Jouer ». Envoyez POST /signup avec {"username": "ada", "password": "correct horse"}. Envoyez POST /token avec le même corps : le statut est 200 et la réponse est {"token": "..."}. Copiez le jeton et envoyez GET /inspect?token=, suivi du jeton. Vous verrez quelque chose comme {"username": "ada", "sub": "66f1c2a9e4b0a1b2c3d4e5f6", "iat": 1793361600, "exp": 1793365200} : exp est postérieur de 3600 secondes à iat. Envoyez POST /token avec {"username": "ada", "password": "wrong horse"} : le statut est 401.
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 revendication 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');
});
Toutes les leçons de Authentification et sécurité
Entraînez-vous par vous-même : Playground Express