Sesi Login
Termasuk bagian Autentikasi dan Keamanan dalam perjalanan Node.js di Coddy. Pelajaran 10 dari 24.
Login memberikan jawaban 200, lalu permintaan berikutnya tidak ingat siapa yang login. Sesi mengingatnya. Saat login, server membuat id yang tidak dapat ditebak siapa pun, menyimpan pengguna pemiliknya, lalu mengirimkannya sebagai cookie. Setiap permintaan berikutnya membawa id itu kembali, lalu server mencarinya.
Id harus acak. Cookie yang menyimpan nama pengguna memungkinkan siapa pun mengetik Cookie: sid=ada dan menjadi Ada. randomBytes(24) memberikan 24 byte acak, 192 bit, yang tidak dapat ditebak siapa pun. toString('base64url') menuliskannya sebagai 32 huruf, angka, -, dan _, karakter yang aman digunakan dalam cookie. Atur cookie httpOnly agar skrip di halaman tidak dapat membacanya.
const cartId = randomBytes(24).toString('base64url');
await carts.insertOne({ _id: cartId, items: [] });
res.cookie('cart', cartId, { httpOnly: true });
// pada permintaan berikutnya
const cart = await carts.findOne({ _id: parseCookies(req.get('cookie')).cart });Tantangan
MudahLangkah ini membuka proyek API catatan pribadi yang disiapkan untuk kursus ini, bukan file proyekmu dari Mengingat dengan Cookies (termasuk server.js). Pekerjaanmu sebelumnya tetap berada di pelajarannya sendiri. Baca file yang disediakan sebelum mengedit server.js. Salinan ini berisi rute pendaftaran dan login untuk catatan pribadi.
Login berhasil, tetapi permintaan berikutnya sudah lupa siapa yang login. Buat server mengingatnya dengan sesi: id acak yang disimpan di server dan dikirim ke browser sebagai cookie.
- Di
server.js, imporrandomBytesdarinode:crypto. Imporsessionsdari./db.jsbersamanotesdanusers.db.jsyang terkunci sudah memiliki koleksisessions. - Di
POST /login, setelah kata sandi diperiksa, buat id sesi: 24 byte acak yang ditulis sebagai teks base64url. Panjangnya 32 karakter, terdiri dari huruf, angka,-dan_, sepertioqjRxvxmXl_nRwAwOLxgZyI2sYq9hZtN. Buat id baru setiap kali login. - Simpan sesi di
sessionssebagai dokumen dengan dua kolom:_id: id sesi.userId:_idpengguna yang login.
- Kirim id ke browser sebagai cookie bernama
sid, dengan opsihttpOnly, agar skrip di halaman tidak dapat membacanya. Login tetap memberikan respons{"username": "ada"}. - Tambahkan
GET /me. Rute ini membaca cookiesid, mencari sesi dengan_idtersebut, lalu mencari pengguna sesi itu. Rute ini memberikan respons{"username": "ada"}. Jika tidak ada cookiesid, atau tidak ada sesi dengan id tersebut, rute ini memberikan status 401 dan{"error": "Log in first"}. Siapa pun yang mengetik cookie secara manual, sepertisid=ada, harus mendapat status 401 itu.
Coba: Tekan Run. Kirim POST /signup dengan {"username": "ada", "password": "correct horse"}. Kirim GET /me: status 401 dan {"error": "Log in first"}. Kirim POST /login dengan isi yang sama seperti saat mendaftar: status 200. Tab Headers pada respons menampilkan Set-Cookie: sid=...; Path=/; HttpOnly, dengan id acakmu sebagai pengganti titik-titik. Kirim GET /me lagi. Browser mengirimkan cookie itu kembali, jadi kamu mendapat {"username": "ada"}. Buka Data: sessions berisi satu dokumen, dengan id-mu sebagai _id dan _id milik ada sebagai userId.
Coba sendiri
import express from 'express';
import { parseCookies } from './cookies.js';
import { notes, 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' });
}
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 pun 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');
});
Semua pelajaran di Autentikasi dan Keamanan
Latihan sendiri: Playground Express