Menu
CoddyTech

التحقق من الرموز

جزء من قسم المصادقة والأمان في رحلة Node.js على Coddy. الدرس 11 من 17.

يرسل عميل API الرمز المميز في ترويسة مع كل طلب: Authorization: Bearer <token>. قراءته باستخدام decodeJwt ستؤدي إلى قبول أي رمز مميز يكتبه أي شخص. تعيد jwtVerify(token, key) حساب التوقيع باستخدام سرك، وتُصدر خطأً عندما لا يتطابق، أو عندما تنتهي صلاحية الرمز المميز، أو عندما لا يكون JWT أصلًا. لا تثق بـ payload إلا بعد أن تُرجع الدالة نتيجة.

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();
}
challenge icon

التحدي

سهل
  • اكتب requireUser: تحقّق من الرمز المميّز Authorization: Bearer باستخدام jwtVerify، واضبط req.user = { id, username } من مطالباته، أو أرجِع الاستجابة 401 { "error": "Log in first" }.
  • يستخدم GET /me هذه الدالة ويُرجع req.user.

أرسل GET /me من دون رمز مميّز. احصل على رمز من POST /token، وأضف الترويسة Authorization: Bearer <token>، ثم أرسل GET /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 });
});

// يعرض محتوى الرمز: 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');
});
quiz iconاختبر نفسك

يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.

جميع دروس المصادقة والأمان