מהו JWT
חלק מהיחידה אימות ואבטחה במסלול ה-Node.js של Coddy. שיעור 9 מתוך 17.
הפעלה זקוקה לחיפוש במסד הנתונים בכל בקשה. JWT (אסימון רשת מסוג JSON) נושא את העובדות בעצמו: שלושה חלקים בקידוד base64url שמחוברים בנקודות. הכותרת מציינת את האלגוריתם, המטען מכיל את הטענות (sub מציין של מי האסימון, exp מציין מתי הוא יפוג), והחתימה מוכיחה שהשרת יצר אותו.
Base64url הוא קידוד, לא הצפנה. כל מי שמחזיק באסימון יכול לקרוא את המטען שלו:
const payload = token.split('.')[1];
JSON.parse(Buffer.from(payload, 'base64url').toString());
// { sub: '42', name: 'Lin', exp: 1767225600 }decodeJwt(token) מתוך jose עושה את אותו הדבר ומדלג על בדיקת החתימה. לכן לעולם אל תכניסו סוד למטען, ולעולם אל תקבלו החלטות על סמך טענה ש-decodeJwt החזירה לכם: כל אחד יכול לכתוב כל טענה.
אתגר
קלGET /inspect?token=...מחזיר את ה-claims של האסימון בפורמט JSON, או 400{ "error": "Not a JWT" }.GET /adminמחזיר 403{ "error": "Admins only" }לכל בקשה.
תחילה שלחו את האסימון של Mallory אל GET /admin?token= ובדקו מה הוא פותח: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJtYWxsb3J5IiwiYWRtaW4iOnRydWV9.c2lnbmF0dXJl. תקנו אותו, ואז שלחו את אותו האסימון אל /inspect.
נסו בעצמכם
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 });
});
// מציג מה כתוב בטוקן: GET /inspect?token=...
app.get('/inspect', (req, res) => {
res.status(501).json({ error: 'Not written yet' });
});
// עמוד המנהל.
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');
});
השיעור הזה כולל חידון קצר. התחילו את השיעור כדי לענות עליו ולעקוב אחרי ההתקדמות.