세션과 인증 — Express Session
이 토픽을 마치면
세션이 왜 필요한지 이해하고, express-session으로 로그인/로그아웃을 구현할 수 있으며, 세션 스토어의 개념과 보안 설정을 알게 됩니다.
왜 세션이 필요한가
쿠키 토픽에서 "HTTP는 무상태"라고 배웠습니다. 쿠키로 상태를 유지할 수 있지만, 쿠키에는 치명적인 한계가 있습니다.
// Bad — 사용자 정보를 쿠키에 직접 저장
res.cookie('user', JSON.stringify({ id: 1, role: 'admin' }));클라이언트(브라우저)가 쿠키를 자유롭게 수정할 수 있습니다. 사용자가 role: 'admin'을 role: 'superadmin'으로 바꿔서 보내면? 서버는 이를 구분할 수 없습니다.
세션은 이 문제를 해결합니다. 민감한 데이터는 서버에 저장하고, 클라이언트에게는 **식별 키(세션 ID)**만 전달합니다.
쿠키 방식: 클라이언트에 "이름=Hoon, 역할=admin" 저장 (위변조 가능)
세션 방식: 클라이언트에 "세션ID=abc123"만 저장 → 서버가 abc123으로 사용자 정보 조회세션의 작동 원리
1. 로그인 요청
Client → Server: POST /login {username: "Hoon", password: "****"}
2. 서버가 세션 생성
Server: 세션 저장소에 {userId: 1, role: "admin"} 저장
Server: 세션 ID = "abc123" 생성
Server → Client: Set-Cookie: connect.sid=abc123
3. 이후 요청
Client → Server: GET /dashboard (Cookie: connect.sid=abc123)
Server: abc123으로 세션 조회 → {userId: 1, role: "admin"} 확인
Server → Client: 대시보드 데이터클라이언트는 세션 ID만 알고 있고, 실제 데이터(userId, role)는 서버 메모리나 데이터베이스에 있습니다. 클라이언트가 세션 ID를 위조해도, 서버의 세션 저장소에 해당 ID가 없으면 인증이 실패합니다.
express-session 설치와 설정
npm install express-sessionconst express = require('express');
const session = require('express-session');
const app = express();
app.use(express.json());
app.use(session({
secret: 'my-secret-key-change-this',
resave: false,
saveUninitialized: false,
cookie: {
httpOnly: true,
secure: false, // 프로덕션에서는 true
maxAge: 3600000 // 1 hour
}
}));| 옵션 | 설명 |
|---|---|
secret | 세션 ID를 암호화하는 키. 길고 랜덤하게 설정 |
resave | 변경 없어도 매 요청마다 세션 저장할지. false 권장 |
saveUninitialized | 빈 세션도 저장할지. false로 하면 로그인 전에는 쿠키 미발행 |
cookie | 세션 쿠키 옵션 (httpOnly, secure, maxAge 등) |
secret은 절대 코드에 하드코딩하면 안 됩니다. 환경 변수로 관리합니다:
secret: process.env.SESSION_SECRET || 'fallback-dev-only'로그인/로그아웃 구현
const users = [
{ id: 1, username: 'alice', password: 'pass123', role: 'admin' },
{ id: 2, username: 'bob', password: 'pass456', role: 'user' }
];
// 로그인
app.post('/login', (req, res) => {
const { username, password } = req.body;
const user = users.find(u => u.username === username && u.password === password);
if (!user) {
return res.status(401).json({ error: 'Invalid credentials' });
}
// 세션에 사용자 정보 저장
req.session.userId = user.id;
req.session.role = user.role;
res.json({ message: `Welcome, ${user.username}` });
});
// 로그아웃
app.post('/logout', (req, res) => {
req.session.destroy(err => {
if (err) {
return res.status(500).json({ error: 'Logout failed' });
}
res.clearCookie('connect.sid');
res.json({ message: 'Logged out' });
});
});req.session은 객체입니다. 어떤 속성이든 자유롭게 저장할 수 있습니다. req.session.destroy()는 서버의 세션 데이터를 삭제합니다.
인증 미들웨어
보호된 라우트에 접근할 때마다 "로그인했는가?"를 확인해야 합니다. 이것을 미들웨어로 만들면 모든 라우트에 재사용할 수 있습니다.
function requireAuth(req, res, next) {
if (!req.session.userId) {
return res.status(401).json({ error: 'Login required' });
}
next();
}
function requireAdmin(req, res, next) {
if (req.session.role !== 'admin') {
return res.status(403).json({ error: 'Admin access required' });
}
next();
}
// 사용
app.get('/profile', requireAuth, (req, res) => {
res.json({ userId: req.session.userId, role: req.session.role });
});
app.get('/admin/dashboard', requireAuth, requireAdmin, (req, res) => {
res.json({ message: 'Admin dashboard' });
});미들웨어를 체이닝해서 "로그인 + 관리자"처럼 복합 조건을 만들 수 있습니다.
세션 스토어 — 메모리 vs 외부 저장소
기본적으로 express-session은 서버 메모리에 세션을 저장합니다. 개발에는 충분하지만 프로덕션에서는 문제가 됩니다.
| 문제 | 설명 |
|---|---|
| 서버 재시작 | 메모리가 초기화되므로 모든 사용자가 로그아웃됨 |
| 메모리 누수 | 세션이 쌓이면 서버 메모리가 부족해짐 |
| 다중 서버 | 서버 A의 메모리에 있는 세션을 서버 B가 모름 |
프로덕션에서는 Redis, MongoDB, PostgreSQL 같은 외부 저장소를 사용합니다:
npm install connect-redis redisconst RedisStore = require('connect-redis').default;
const { createClient } = require('redis');
const redisClient = createClient();
redisClient.connect();
app.use(session({
store: new RedisStore({ client: redisClient }),
secret: process.env.SESSION_SECRET,
resave: false,
saveUninitialized: false
}));Redis에 세션을 저장하면 서버가 재시작되어도 세션이 유지되고, 여러 서버가 같은 Redis를 바라보면 세션을 공유할 수 있습니다.
세션 vs JWT — 언제 무엇을
| 세션 | JWT | |
|---|---|---|
| 상태 저장 | 서버 (stateful) | 클라이언트 (stateless) |
| 스케일링 | Redis 등 공유 저장소 필요 | 서버 간 공유 불필요 |
| 로그아웃 | destroy() 즉시 무효화 | 토큰 만료까지 유효 (블랙리스트 필요) |
| 보안 | 서버가 데이터 관리 | 토큰 탈취 시 대응 어려움 |
| 적합한 사례 | 전통적 웹 앱, 관리자 페이지 | API 서버, 모바일 앱, 마이크로서비스 |
전통적 웹 앱 (SSR) → 세션이 자연스러움
SPA + API 서버 → JWT가 편리함
모바일 앱 → JWT (쿠키 관리가 복잡)핵심 정리
| 개념 | 정리 |
|---|---|
| 세션 | 사용자 데이터를 서버에 저장, 클라이언트에는 ID만 전달 |
| 세션 ID | 쿠키로 전달되는 식별 키 (connect.sid) |
req.session | 세션 데이터 읽기/쓰기 객체 |
destroy() | 로그아웃 — 세션 데이터 삭제 |
| 세션 스토어 | 프로덕션에서는 Redis 등 외부 저장소 사용 |
세션 기반 인증의 핵심: 민감한 데이터는 서버에, 클라이언트에는 열쇠(세션 ID)만. 이 원칙을 이해하면, 토큰 기반 인증(JWT)과의 차이도 자연스럽게 파악할 수 있습니다.