Menu
CoddyTech

로그인이 필요한 라우트

Coddy Node.js 여정의 인증과 보안 섹션에 포함된 레슨. 17개 중 6번째.

모든 비공개 경로에는 GET /me에서 사용하는 세션 조회가 필요합니다. 각 경로에 복사해 넣으면 한 번은 빠뜨리게 되고, 그러면 해당 경로가 누구에게나 열리게 됩니다. 대신 미들웨어로 한 번만 작성하세요.

미들웨어는 경로보다 먼저 실행됩니다. 응답을 보내고 실행을 멈추거나, 찾은 내용을 req에 넣고 next()를 호출합니다. 필요한 각 경로에 핸들러보다 앞서 전달하세요.

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));

return이 중요합니다. 이것이 없으면 경로도 실행됩니다.

challenge icon

챌린지

쉬움
  • requireUser를 작성하세요: 유효한 세션이 없으면 401 { "error": "Log in first" }을 반환하고, 그렇지 않으면 req.user = { id, username }을 설정하고 next()를 호출합니다.
  • GET /me, GET /notes, POST /notes에서 이를 사용합니다. 노트에는 owner: req.user.id를 저장하고, GET /notes는 자신의 노트만 나열합니다.

로그아웃한 상태에서 GET /notes를 요청한 다음, 로그인하고 노트를 추가하세요.

직접 해보기

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 });
});

// 이 브라우저가 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 });
});

// 메모. 현재는 누구나 읽고 쓸 수 있습니다.
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');
});
quiz icon실력 점검

이 레슨에는 짧은 퀴즈가 포함되어 있습니다. 레슨을 시작해 문제를 풀고 진행 상황을 기록하세요.

인증과 보안의 모든 레슨