로그아웃
Coddy Node.js 여정의 인증과 보안 섹션에 포함된 레슨. 24개 중 12번째.
쿠키를 지우는 것만으로는 브라우저에 id를 잊으라고 요청할 뿐입니다. 공유 컴퓨터에서든 도난당한 쿠키에서든 id를 복사한 사람은 여전히 작동하는 키를 가지고 있습니다. 로그아웃하려면 서버에서 세션을 삭제해야 합니다.
res.clearCookie(name)은 브라우저에 쿠키를 삭제하라고 알립니다. 상태 코드 204 No Content는 작업이 성공했고 돌려보낼 내용이 없다는 뜻입니다: res.status(204).end().
다음은 관련 경로로, 모든 기기에서 한 번에 사용자를 로그아웃시킵니다:
app.post('/logout-everywhere', requireUser, async (req, res) => {
await sessions.deleteMany({ userId: new ObjectId(req.user.id) });
res.clearCookie('sid');
res.status(204).end();
});챌린지
쉬움로그아웃하는 방법을 추가하세요. 쿠키를 삭제하는 것만으로는 충분하지 않습니다. 세션 ID를 복사한 사람은 계속 사용할 수 있기 때문입니다. 따라서 서버에서도 세션을 잊도록 합니다.
server.js에POST /logout을 추가하세요. 본문은 필요하지 않습니다.sid쿠키를 읽으세요. 쿠키가 있으면sessions에서 해당_id를 가진 세션을 삭제하세요.- 브라우저에
sid쿠키를 잊도록 알리세요. - 상태 코드 204와 빈 본문으로 응답하세요. 204 콘텐츠 없음은 요청이 성공했고 보낼 내용이 없다는 뜻입니다. 요청에
sid쿠키가 없더라도 204로 응답하세요.
직접 해 보세요: 실행을 누르세요. POST /signup을 보낸 다음 POST /login을 보내세요. 두 요청 모두 {"username": "ada", "password": "correct horse"}를 사용합니다. GET /me는 {"username": "ada"}를 반환합니다. POST /logout을 보내세요. 상태 코드는 204이고 본문은 비어 있습니다. 다시 GET /me를 보내세요. 상태 코드는 401이고 {"error": "Log in first"}가 반환됩니다. 데이터를 여세요. sessions가 비어 있습니다. POST /logout을 한 번 더 보내세요. 이번에도 204입니다.
직접 해보기
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 });
});
// 세션 쿠키에 해당하는 사용자를 찾고, 찾지 못하면 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 });
});
// 이 브라우저가 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 });
});
// Notes: 각 사용자는 자신의 노트만 읽고 씁니다.
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');
});
인증과 보안의 모든 레슨
직접 연습해 보세요: Express 플레이그라운드