Đăng xuất
Thuộc phần Xác thực và bảo mật trong hành trình Node.js của Coddy. Bài học 12 trên 24.
Chỉ xóa cookie thì trình duyệt mới quên mã định danh. Bất kỳ ai đã sao chép mã định danh đó từ máy tính dùng chung hoặc cookie bị đánh cắp vẫn giữ khóa có thể sử dụng. Khi đăng xuất, máy chủ phải xóa phiên.
res.clearCookie(name) yêu cầu trình duyệt xóa cookie. Trạng thái 204 No Content nghĩa là thao tác đã thành công và không có gì để gửi lại: res.status(204).end().
Đây là một route liên quan, cho phép đăng xuất người dùng trên mọi thiết bị cùng lúc:
app.post('/logout-everywhere', requireUser, async (req, res) => {
await sessions.deleteMany({ userId: new ObjectId(req.user.id) });
res.clearCookie('sid');
res.status(204).end();
});Thử thách
DễThêm cách để đăng xuất. Chỉ xóa cookie thôi là chưa đủ, vì bất kỳ ai đã sao chép mã phiên đều có thể tiếp tục dùng nó. Vì vậy, máy chủ cũng quên phiên đó.
- Trong
server.js, thêmPOST /logout. Không cần phần thân. - Đọc cookie
sid. Nếu có cookie này, hãy xóa phiên có_idđó khỏisessions. - Bảo trình duyệt quên cookie
sid. - Trả về trạng thái 204 và không có phần thân. 204 No Content nghĩa là thao tác đã thành công và không có gì để gửi lại. Vẫn trả về 204 ngay cả khi yêu cầu không có cookie
sid.
Thử nhé: Nhấn Chạy. Gửi POST /signup, rồi POST /login, cả hai đều có {"username": "ada", "password": "correct horse"}. GET /me trả về {"username": "ada"}. Gửi POST /logout: trạng thái 204 và phần thân trống. Gửi lại GET /me: trạng thái 401 và {"error": "Log in first"}. Mở Data: sessions trống. Gửi POST /logout thêm một lần nữa: vẫn là 204.
Tự làm thử
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 });
});
// Tìm người dùng qua cookie phiên, hoặc trả về 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 });
});
// Đếm số lần trình duyệt này gọi 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 });
});
// Ghi chú: mỗi người dùng chỉ đọc và viết ghi chú của mình.
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');
});
Tất cả bài học trong Xác thực và bảo mật
Tự luyện tập: Playground Express