JWT 인증 방식에 이어, 세션 인증 방식을 알아보자.
세션 인증 방식은 웹 애플리케이션에서 사용자의 신원을 확인하고 그 세션 동안의 활동을 추적하는 데 사용된다. 브라우저 중심의 인증방식인 JWT에 비해, 세션 방식은 서버 중심의 인증 방식이다.
※ 세션(session) 방식은 영구적인 데이터 유지가 아닌 사용자가 로그인한 후 로그아웃할 때까지의 데이터를 일시적으로 유지하는 방식이다.
JWT와 세션 방식 모두 각자의 장단점이 있지만 세션을 사용하는 이유가 뭘까?
세션은 메모리 공간 처럼 다양한 임시 데이터들을 저장하고 사용할 수 있다. 예를 들어, 사용자의 정보, 장바구니, 글 임시 저장 등에 사용될 수 있다. 보안적인 측면을 이유로 중요한 정보는 세션 스토어에 저장하는 것은 좋지 않다.
한 번의 인증 후 서버에서 세션 ID를 발급하면 세션이 유지되어, 각 요청마다 재인증할 필요가 없어 성능상 이점이 있다.
세션 방식의 단점은 다음과 같다.
세션 방식은 모든 저장과 인증이 서버에서 작동하기 때문에 비용은 곧 우리(개발자)가 부담한다.
여러 개의 서버가 동일한 웹을 서비스 하는 경우 데이터 공유가 어려울 수 있다. 그러나 이것은 스토어를 메모리 방식으로 할 경우이며, 외부 세션 저장소(Redis, MongoDB 등)을 사용한다면 해결되는 문제이다.
다음은 세션 인증 방식이 어떻게 흘러가는지 나타낸다.
기본적으로 세션 저장소를 설정하지 않으면 서버 메모리로 저장하며, 서버를 종료하면 세션 데이터가 모두 날아가는 휘발성이다. 세션 저장소로는 서버 메모리, redis, mongodb 등 다양한 곳에서 세션 저장소를 제공한다.
세션 방식은 쿠키를 이용한 인증 방식이기 때문에 JWT의 access token과 마찬가지로 session id 또한 보안 처리를 해주어야 한다.
대표적으로 XSS, 하이재킹 등을 방어하기 위해 쿠키의 httpOnly와 secure속성을 반드시 적용시켜야 한다. 또한 maxAge속성으로 일정 시간이 지나면 세션을 만료하는 것도 좋다.
express-session, typescript를 사용하고 파일 스토어 방식으로 구현 하였다. 전체 코드는 깃허브에 있다.
만약 typescript 설정을 모른다면 깃허브 전체 코드를 가져오자.
npm i express-session @types/express-session
npm i session-file-store @types/session-file-store
import session from "express-session";
import sessionFileStore from "session-file-store";
const FileStore = sessionFileStore(session);
app.use(
session({
secret: "your-secret-key", // 세션 암호화에 사용되는 비밀 키
resave: false, // 세션이 수정되지 않아도 항상 저장되도록 설정
saveUninitialized: true, // 초기화되지 않은 세션을 저장할지 여부 설정
cookie: {
secure: true,
httpOnly: true,
maxAge: 6000,
}, // HTTPS를 사용할 경우 true로 설정
store: new FileStore(),
})
);
로직 예시로 4가지 api 엔드 포인트를 구현 하였다.
// 홈 라우트
app.get("/", (req: Request, res: Response) => {
// 세션에 카운트 저장 및 증가
if (req.session.views) {
req.session.views++;
} else {
req.session.views = 1;
}
res.send(req.session);
});
// 로그인 라우트
app.get("/login", (req: Request, res: Response) => {
req.session.user = { username: "user1" };
res.send(req.session);
});
// 로그아웃 라우트
app.get("/logout", (req: Request, res: Response) => {
req.session.destroy((err) => {
if (err) {
return res.status(500).send("Failed to log out");
}
res.send(req.session);
});
});
// 인증된 사용자만 접근 가능한 라우트
app.get("/protected", (req: Request, res: Response) => {
if (req.session.user) {
res.send(req.session);
} else {
res.status(401).send("Unauthorized");
}
});
세션 방식으로 file store방식은 잘 사용하지 않으므로, 외부 데이터베이스 스토어 사용을 추천한다.