Menu
CoddyTech

Rute di Balik Login

Termasuk bagian Autentikasi dan Keamanan dalam perjalanan Node.js di Coddy. Pelajaran 6 dari 17.

Setiap rute privat memerlukan pencarian sesi dari GET /me. Jika disalin ke setiap rute, hal ini akan terlupakan di salah satu rute, sehingga rute itu terbuka untuk semua orang. Tulis sekali saja sebagai middleware.

Middleware berjalan sebelum rute. Middleware akan memberikan respons lalu berhenti, atau menaruh apa yang ditemukan pada req dan memanggil next(). Berikan middleware ini ke setiap rute yang memerlukannya, sebelum handler:

function requireAdult(req, res, next) {
    if (Number(req.query.age) < 18) {
        return res.status(403).json({ error: 'Adults only' });
    }
    req.adult = true;
    next();
}

app.get('/wines', requireAdult, (req, res) => res.json(wines));

return itu penting: tanpanya, rute juga akan dijalankan.

challenge icon

Tantangan

Mudah
  • Tulis requireUser: kirim 401 { "error": "Log in first" } tanpa sesi yang valid, jika tidak, tetapkan req.user = { id, username } dan panggil next().
  • GET /me, GET /notes, dan POST /notes menggunakannya. Catatan menyimpan owner: req.user.id, dan GET /notes hanya menampilkan catatan milikmu.

Kirim GET /notes saat belum login, lalu login dan tambahkan catatan.

Coba sendiri

import express from 'express';
import { randomBytes } from 'node:crypto';
import { parseCookies } from './cookies.js';
import { notes, sessions, 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' });
    }
    const sid = randomBytes(24).toString('base64url');
    await sessions.insertOne({ _id: sid, userId: user._id, createdAt: new Date() });
    res.cookie('sid', sid, { httpOnly: true });
    res.json({ username: user.username });
});

app.get('/me', async (req, res) => {
    const { sid } = parseCookies(req.get('cookie'));
    const session = sid && (await sessions.findOne({ _id: sid }));
    if (!session) {
        return res.status(401).json({ error: 'Log in first' });
    }
    const user = await users.findOne({ _id: session.userId });
    res.json({ username: user.username });
});

// Menghitung berapa kali browser ini mengakses 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 });
});

// Catatan. Untuk saat ini, siapa saja bisa membaca dan menulis catatan.
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');
});
quiz iconUji dirimu

Pelajaran ini berisi kuis singkat. Mulai pelajarannya untuk menjawab kuis dan melacak progresmu.

Semua pelajaran di Autentikasi dan Keamanan

Latihan sendiri: Playground Express