ما هو 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=...مطالبات الرمز بصيغة 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');
});
يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.