توقيع الرموز
جزء من قسم المصادقة والأمان في رحلة Node.js على Coddy. الدرس 10 من 17.
لا تكون قيمة الرمز كبيرة إلا إذا لم يستطع أي شخص آخر إنشاء رمز مثله. توقّعه jose باستخدام HS256: قيمة HMAC للترويسة والحمولة، ومفتاحها سرّ لا يعرفه إلا الخادم.
يأتي السر من متغير بيئة، وهو process.env.JWT_SECRET، وليس من الشيفرة مطلقًا: فالشيفرة مشتركة ومنسوخة ومودعة في المستودع. يضبط هذا الدرس JWT_SECRET لك. وتريده jose على هيئة بايتات.
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 نصًا، بينما تأخذ setExpirationTime مدةً مثل '30m' أو '2d'.
التحدي
سهل- يردّ
POST /tokenمع{ username, password }بـ{ token }: باستخدام HS256، وموقّعًا بـJWT_SECRET، والادعاءusername، والموضوع هو معرّف المستخدم كنص، وتنتهي صلاحيته بعد ساعة واحدة. - تسجيل الدخول ببيانات خاطئة يردّ بالرمز 401
{ "error": "Wrong username or password" }.
أنشئ حسابًا، واحصل على رمز، والصقه في GET /inspect?token=.
جرّب بنفسك
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) => {
try {
res.json(decodeJwt(String(req.query.token ?? '')));
} catch {
res.status(400).json({ error: 'Not a JWT' });
}
});
// صفحة المسؤول. لا يمكن الوثوق بأي ادعاء قبل التحقق من توقيعه.
app.get('/admin', (req, res) => {
res.status(403).json({ error: 'Admins only' });
});
app.listen(3000, () => {
console.log('Vault is running on port 3000');
});