Menu
CoddyTech

Sessions להתחברות

חלק מהיחידה אימות ואבטחה במסלול ה-Node.js של Coddy. שיעור 10 מתוך 24.

התחברות מחזירה 200, ואז הבקשה הבאה שכחה מי התחבר. הפעלה זוכרת. בזמן ההתחברות השרת יוצר מזהה שאיש לא יכול לנחש, שומר לאיזה משתמש הוא שייך ושולח אותו כעוגייה. כל בקשה מאוחרת יותר מחזירה את המזהה, והשרת מחפש אותו.

המזהה חייב להיות אקראי. עוגייה שמכילה את שם המשתמש תאפשר לכל אחד להקליד Cookie: sid=ada ולהיות Ada. randomBytes(24) מספק 24 בתים אקראיים, 192 ביטים, שאיש לא יכול לנחש. toString('base64url') כותב אותם באמצעות 32 אותיות, ספרות, - ו-_, תווים שבטוחים לשימוש בעוגייה. הגדירו את העוגייה עם 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

אתגר

קל

השלב הזה פותח את פרויקט ה-API המוכן של הקורס להערות פרטיות, במקום את קובצי הפרויקט שלך מתוך זוכרים באמצעות עוגיות (כולל server.js). העבודה הקודמת שלך נשארת בשיעור משלה. קרא את הקבצים שסופקו לפני עריכת server.js. העותק הזה מכיל נתיבים להרשמה ולהתחברות להערות פרטיות.

ההתחברות עובדת, אבל הבקשה הבאה כבר שכחה מי התחבר. גרום לשרת לזכור באמצעות הפעלה: מזהה אקראי שנשמר בשרת ונשלח לדפדפן כעוגייה.

  1. ב-server.js, ייבא את randomBytes מתוך node:crypto. ייבא את sessions מתוך ./db.js לצד notes ו-users. בקובץ db.js הנעול כבר יש אוסף sessions.
  2. ב-POST /login, אחרי בדיקת הסיסמה, צור מזהה להפעלה: 24 בתים אקראיים שנכתבים כטקסט base64url. מדובר ב-32 תווים של אותיות, ספרות, - ו-_, כמו oqjRxvxmXl_nRwAwOLxgZyI2sYq9hZtN. צור מזהה חדש בכל התחברות.
  3. שמור את ההפעלה ב-sessions כמסמך עם שני שדות:
    • _id: מזהה ההפעלה.
    • userId: ה-_id של המשתמש המחובר.
  4. שלח את המזהה לדפדפן כעוגייה בשם sid, עם האפשרות httpOnly, כדי שסקריפטים בדף לא יוכלו לקרוא אותה. ההתחברות עדיין מחזירה {"username": "ada"}.
  5. הוסף את GET /me. הוא קורא את עוגיית sid, מוצא את ההפעלה עם אותו _id, ואז מוצא את המשתמש של אותה הפעלה. הוא מחזיר {"username": "ada"}. כשאין עוגיית sid, או שאין הפעלה עם המזהה הזה, הוא מחזיר את סטטוס 401 ואת {"error": "Log in first"}. מי שמקליד עוגייה ידנית, כמו sid=ada, חייב לקבל את סטטוס 401 הזה.

נסה זאת: לחץ על Run. שלח POST /signup עם {"username": "ada", "password": "correct horse"}. שלח GET /me: סטטוס 401 והתגובה {"error": "Log in first"}. שלח POST /login עם אותו גוף כמו בהרשמה: סטטוס 200. בלשונית Headers של התגובה מופיע Set-Cookie: sid=...; Path=/; HttpOnly, כשהמזהה האקראי שלך מופיע במקום הנקודות. שלח שוב GET /me. הדפדפן שולח את העוגייה בחזרה, ולכן תקבל {"username": "ada"}. פתח את Data: ב-sessions מופיע מסמך אחד, עם המזהה שלך בתור _id ועם ה-_id של ada בתור userId.

נסו בעצמכם

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');
});

כל השיעורים ביחידה אימות ואבטחה

תרגלו בעצמכם: Express אונליין