Rute yang Dilindungi Login
Termasuk bagian Autentikasi dan Keamanan dalam perjalanan Node.js di Coddy. Pelajaran 11 dari 24.
Setiap route privat memerlukan pencarian sesi dari GET /me. Jika disalin ke setiap route, pencarian itu bisa terlupakan, sehingga route tersebut terbuka untuk semua orang. Tulis sekali saja sebagai middleware.
Middleware berjalan sebelum route. Saat middleware menyimpan sesuatu pada req, seperti req.user, route membacanya dari sana. _id pengguna adalah ObjectId; user._id.toHexString() menghasilkan nilainya dalam bentuk teks. Middleware akan memberikan respons lalu berhenti, atau menyimpan hasil temuannya pada req dan memanggil next(). Teruskan middleware itu ke setiap route 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));Kata kunci return itu penting: tanpanya, route juga akan dijalankan.
Tantangan
MudahCatatan masih bisa diakses semua orang. Jadikan catatan pribadi: hanya pengguna yang sudah masuk yang bisa membaca atau menambahkan catatan, dan setiap pengguna hanya melihat catatannya sendiri.
- Di
server.js, tulis fungsi middlewarerequireUser(req, res, next). Pindahkan pencarian sesi dariGET /meke fungsi ini: baca cookiesid, cari sesi, lalu cari penggunanya.- Jika tidak ada pengguna seperti itu, kirim respons dengan status 401 dan
{"error": "Log in first"}, lalu berhenti. - Jika ada, tempatkan pengguna pada permintaan sebagai
req.user, sebuah object dengan dua field, lalu panggilnext(). Kedua field tersebut adalahid, yaitu_idpengguna dalam bentuk teks, danusername. MethodtoHexString()milik ObjectId menghasilkan teksnya, seperti"66f1c2a9e4b0a1b2c3d4e5f6".
- Jika tidak ada pengguna seperti itu, kirim respons dengan status 401 dan
- Berikan
requireUserkeGET /me,GET /notes, danPOST /notes, sebelum fungsi masing-masing rute. Setelah itu,GET /memengirim respons{"username": "ada"}, yang dibaca darireq.user. - Di
POST /notes, simpan setiap catatan dengan fieldowneryang berisireq.user.id. Responsnya adalah catatan baru:{"owner": "66f1c2a9e4b0a1b2c3d4e5f6", "text": "Door code 1234", "_id": "66f1c2b0e4b0a1b2c3d4e5f7"} - Di
GET /notes, kembalikan hanya catatan yangowner-nya adalah pengguna yang sudah masuk.
Coba: Tekan Jalankan. Kirim GET /notes: status 401 dan {"error": "Log in first"}. Kirim POST /signup, lalu POST /login, keduanya dengan {"username": "ada", "password": "correct horse"}. Kirim POST /notes dengan {"text": "Door code 1234"}: status 201 dan catatan dengan owner-nya. Kirim GET /notes: list berisi satu catatan itu. Sekarang daftar dan masuk dengan {"username": "bob", "password": "correct horse"}. GET /notes mengembalikan [], karena bob belum menulis apa pun.
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 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. Untuk saat ini, siapa saja bisa membaca dan menulisnya.
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');
});
Pelajaran ini berisi kuis singkat. Mulai pelajarannya untuk menjawab kuis dan melacak progresmu.
Semua pelajaran di Autentikasi dan Keamanan
2Sesi dan Cookie
Mengingat dengan CookiePencarian SesiSesi LoginRute yang Dilindungi LoginLatihan sendiri: Playground Express