Menu
CoddyTech

جلسات تسجيل الدخول

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

يُرجع تسجيل الدخول الرمز 200، ثم ينسى الطلب التالي مَن سجّل الدخول. الجلسة تتذكّر ذلك. عند تسجيل الدخول، ينشئ الخادم معرّفًا لا يستطيع أحد تخمينه، ويخزّن المستخدم الذي ينتمي إليه، ويرسله على هيئة ملف تعريف ارتباط. يعيد كل طلب لاحق المعرّف، ويبحث عنه الخادم.

يجب أن يكون المعرّف عشوائيًا. فملف تعريف ارتباط يحتوي على اسم المستخدم سيسمح لأي شخص بكتابة Cookie: sid=ada وانتحال شخصية Ada. توفّر randomBytes(24) عدد 192 بت عشوائيًا. اضبط ملف تعريف الارتباط على httpOnly كي لا تتمكن البرامج النصية في الصفحة من قراءته.

const cartId = randomBytes(24).toString('base64url');
await carts.insertOne({ _id: cartId, items: [] });
res.cookie('cart', cartId, { httpOnly: true });

// في طلب لاحق
const cart = await carts.findOne({ _id: parseCookies(req.get('cookie')).cart });
challenge icon

التحدي

سهل
  • يخزّن POST /login القيمة { _id: sid, userId } في sessions ويضبط ملف تعريف الارتباط sid على httpOnly، باستخدام randomBytes(24).toString('base64url').
  • يعيد GET /me القيمة { username } لتلك الجلسة، أو الحالة 401 مع { "error": "Log in first" }.

أنشئ حسابًا، وسجّل الدخول، وأرسل GET /me، ثم ألقِ نظرة على Data.

جرّب بنفسك

import express from 'express';
import { parseCookies } from './cookies.js';
import { notes, 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 إلى /visit.
app.get('/visit', (req, res) => {
    const cookies = parseCookies(req.get('cookie'));
    const visits = Number(cookies.visits ?? 0) + 1;
    res.cookie('visits', String(visits));
    res.json({ visits });
});

// الملاحظات. يمكن لأي شخص قراءتها والكتابة فيها في الوقت الحالي.
app.get('/notes', async (req, res) => {
    res.json(await notes.find().toArray());
});

app.post('/notes', async (req, res) => {
    const note = { text: String(req.body.text ?? '') };
    await notes.insertOne(note);
    res.status(201).json(note);
});

app.listen(3000, () => {
    console.log('Vault is running on port 3000');
});

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