Menu
CoddyTech

Keluar

Termasuk bagian Autentikasi dan Keamanan dalam perjalanan Node.js di Coddy. Pelajaran 12 dari 24.

Menghapus cookie hanya meminta browser melupakan id. Siapa pun yang menyalin id itu dari komputer bersama atau cookie yang dicuri tetap memegang kunci yang berfungsi. Untuk keluar, sesi harus dihapus di server.

res.clearCookie(name) memberi tahu browser untuk membuang cookie. Status 204 No Content berarti prosesnya berhasil dan tidak ada yang perlu dikirim kembali: res.status(204).end().

Berikut rute terkait untuk mengeluarkan pengguna dari semua perangkat sekaligus:

app.post('/logout-everywhere', requireUser, async (req, res) => {
    await sessions.deleteMany({ userId: new ObjectId(req.user.id) });
    res.clearCookie('sid');
    res.status(204).end();
});
challenge icon

Tantangan

Mudah

Tambahkan cara untuk logout. Menghapus cookie saja tidak cukup, karena siapa pun yang menyalin id sesi bisa terus menggunakannya. Jadi, server juga menghapus sesi tersebut.

  1. Di server.js, tambahkan POST /logout. Rute ini tidak memerlukan body.
  2. Baca cookie sid. Jika ada, hapus sesi dengan _id tersebut dari sessions.
  3. Minta browser melupakan cookie sid.
  4. Berikan respons dengan status 204 dan tanpa body. 204 No Content berarti permintaan berhasil dan tidak ada yang perlu dikirim kembali. Berikan respons 204 meskipun permintaan tidak memiliki cookie sid.

Coba: Tekan tombol “Jalankan”. Kirim POST /signup, lalu POST /login, keduanya dengan {"username": "ada", "password": "correct horse"}. GET /me mengembalikan {"username": "ada"}. Kirim POST /logout: status 204 dan body kosong. Kirim GET /me lagi: status 401 dan {"error": "Log in first"}. Buka Data: sessions kosong. Kirim POST /logout sekali lagi: tetap 204.

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

// Mencari user dari cookie sesi, atau mengembalikan 401.
async function requireUser(req, res, next) {
    const { sid } = parseCookies(req.get('cookie'));
    const session = sid && (await sessions.findOne({ _id: sid }));
    const user = session && (await users.findOne({ _id: session.userId }));
    if (!user) {
        return res.status(401).json({ error: 'Log in first' });
    }
    req.user = { id: user._id.toHexString(), username: user.username };
    next();
}

app.get('/me', requireUser, (req, res) => {
    res.json({ username: req.user.username });
});

// Menghitung berapa kali browser ini memanggil 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: setiap user hanya membaca dan menulis catatannya sendiri.
app.get('/notes', requireUser, async (req, res) => {
    res.json(await notes.find({ owner: req.user.id }).toArray());
});

app.post('/notes', requireUser, async (req, res) => {
    const note = { owner: req.user.id, 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');
});

Semua pelajaran di Autentikasi dan Keamanan

Latihan sendiri: Playground Express