Vérifier des jetons
Fait partie de la section Authentification et sécurité du Journey Node.js de Coddy. Leçon 16 sur 24.
Un client API envoie son jeton dans un en-tête à chaque requête : Authorization: Bearer <token>. Le lire avec decodeJwt accepterait n’importe quel jeton saisi par n’importe qui. jwtVerify(token, key) recalcule la signature avec votre secret et lève une exception si elle ne correspond pas, si le jeton a expiré ou s’il ne s’agit pas du tout d’un JWT. Vous ne pouvez faire confiance à payload qu’après son retour.
async function requireDevice(req, res, next) {
const token = (req.get('authorization') ?? '').replace(/^Bearer /, '');
try {
const { payload } = await jwtVerify(token, deviceKey);
req.device = payload.sub;
} catch {
return res.status(401).json({ error: 'Unknown device' });
}
next();
}Défi
FacileAccepte maintenant les jetons que tu signes. Une application envoie son jeton dans un en-tête à chaque requête, et le serveur vérifie la signature avant de faire confiance à quoi que ce soit qu’il contient.
- Dans
server.js, importejwtVerifydepuisjose. - Écris un middleware
requireUser(req, res, next). Il lit l’en-têteAuthorization. Cet en-tête contient le motBearer, une espace, puis le jeton. Extrais-en le jeton. - Vérifie le jeton avec
jwtVerify, en utilisant la même clé que pour la signature.jwtVerifydéclenche une exception lorsque la signature ne correspond pas, lorsque le jeton a expiré ou lorsque le texte n’est pas du tout un jeton. Dans tous ces cas, ainsi que lorsqu’il n’y a pas d’en-tête, réponds avec le statut 401 et{"error": "Log in first"}. - Lorsque le jeton est valide, définis
req.usercomme un objet contenantid, tiré de la revendicationsubdu jeton, etusername, tiré de sa revendicationusername. Appelle ensuitenext(). - Ajoute
GET /meavecrequireUser. Cette route renvoiereq.user:{"id": "66f1c2a9e4b0a1b2c3d4e5f6", "username": "ada"}.
Essaie : Appuie sur « Jouer ». Envoie GET /me : statut 401 et {"error": "Log in first"}. Envoie POST /signup, puis POST /token, les deux avec {"username": "ada", "password": "correct horse"}, et copie le jeton. Dans le champ des en-têtes, ajoute un en-tête nommé Authorization dont la valeur est Bearer, une espace, puis ton jeton. Envoie GET /me : statut 200, avec ton identifiant et "username": "ada". Modifie maintenant une lettre au milieu du jeton, entre les deux points, puis envoie de nouveau GET /me : statut 401, car la signature ne correspond plus.
Essayez vous-même
import express from 'express';
import { SignJWT, decodeJwt } from 'jose';
import { users } from './db.js';
import { hashPassword, verifyPassword } from './passwords.js';
const secret = new TextEncoder().encode(process.env.JWT_SECRET);
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 });
});
app.post('/token', 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' });
}
const token = await new SignJWT({ username: user.username })
.setProtectedHeader({ alg: 'HS256' })
.setSubject(user._id.toHexString())
.setIssuedAt()
.setExpirationTime('1h')
.sign(secret);
res.json({ token });
});
// Affiche le contenu d’un jeton : 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 affirmation 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');
});
Cette leçon comprend un petit quiz. Commencez la leçon pour y répondre et suivre votre progression.
Toutes les leçons de Authentification et sécurité
Entraînez-vous par vous-même : Playground Express