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

// מציג את תוכן ה-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בחנו את עצמכם

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

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