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();
});Tantangan
MudahTambahkan cara untuk logout. Menghapus cookie saja tidak cukup, karena siapa pun yang menyalin id sesi bisa terus menggunakannya. Jadi, server juga menghapus sesi tersebut.
- Di
server.js, tambahkanPOST /logout. Rute ini tidak memerlukan body. - Baca cookie
sid. Jika ada, hapus sesi dengan_idtersebut darisessions. - Minta browser melupakan cookie
sid. - 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