발단: 레거시에 있던 Express 서버를 보고, 이미 서버가 있는데 왜 필요하지?
레거시 프로젝트(Express 풀스택)와 새로운 모노레포 프로젝트를 동시에 보고 있는데, 문득 이상한 점을 발견했다.
모노레포에서는 BFF라는 서버를 사용해 프록시 및 토큰을 사용하는데, 레거시 프로젝트에도 Express를 쓰고 있던걸 발견했다.
이미 서버가 있는데 express? 설마 예전 레거시에서 express도 bff의 역할을 한건가?
근데 프록시를 한다면 프록시 서버도 있던걸로 아는데, 프록시 서버에서 토큰은 관리 못할라나..
레거시에서도 bff를 도입할 수 있었던가..?
여기서부터 꼬리에 꼬리를 물고 파보기 시작했다.
프록시 서버, BFF — 비슷한데 뭐가 다를까?
처음엔 "둘 다 중간에서 요청 전달해주는 거 아냐?" 라고 생각했다. 그런데 파보니까 확실히 달랐다.
1. 직접 호출 (중간 서버 없음)
브라우저 → 백엔드 API (직접)
- 프론트에서 백엔드 도메인을 직접 호출
- API 주소가 브라우저 Network 탭에 그대로 노출
- CORS 문제를 프론트/백엔드 양쪽에서 해결해야 함
- 토큰은 프론트(상태관리, localStorage)에서 관리
2. 프록시 서버
브라우저 → 프록시 서버 → 백엔드 API
- 요청을 받아서 백엔드로 그대로 전달만 함
- CORS 우회, 백엔드 도메인 은닉 정도의 역할
- 데이터 가공 없음 — 백엔드 응답 그대로 내려줌
- 토큰은 여전히 프론트가 관리
3. BFF (Backend For Frontend)
브라우저 → BFF 서버 → 백엔드 API
- 프록시 기능은 기본이고, 그 위에 더 많은 일을 함
- 여러 API를 조합해서 프론트에 맞는 응답으로 가공
- 토큰/인증을 서버(httpOnly 쿠키)에서 관리
- 프론트는 BFF 하나만 바라보면 됨
이걸 표로 정리하면:
| 직접 호출 | 프록시 서버 | BFF | |
|---|---|---|---|
| API 은닉 | X (다 보임) | O | O |
| CORS 해결 | X | O | O |
| 데이터 가공 | X | X (그대로 전달) | O (프론트 맞춤) |
| API 조합 | X | X | O (여러 API → 하나로) |
| 토큰 관리 | 프론트 (상태관리) | 프론트 (상태관리) | 서버 (httpOnly 쿠키) |
| 보안 | 취약 | 보통 | 강함 |
핵심은, 프록시 서버는 "전달만" 하니까 토큰은 프론트가 들고 있어야 한다.
BFF는 프론트를 위한 "전용 백엔드"이기 때문에 토큰까지 서버가 책임진다.
프록시에서 한 단계 진화한 게 BFF인 셈이다.
그래서 레거시 Express는 뭐였을까?
여기까지 정리하고 나니, 다시 레거시 프로젝트가 궁금해졌다. 우리 Express 서버는 단순 프록시일까, BFF일까?
코드를 확인해봤더니:
- 프론트 요청을 받아서 axios로 원격 백엔드 API를 대신 호출하고 있었다
- 인증 쿠키를 Express가 직접 관리하고 백엔드에 전달하고 있었다
- 응답 데이터를 가공해서 프론트에 내려주는 부분도 있었다
단순 전달이 아니라 가공과 인증까지 — 사실상 BFF였다.
# 레거시 (Express 풀스택)
브라우저 → Express (프론트 서빙 + BFF 겸업) → 백엔드 API
# 모노레포
브라우저 → Vue 앱 (프론트) → BFF 서버 (독립) → 백엔드 API
차이는 분리에 있었다. 레거시는 하나의 Express가 프론트 서빙도 하고 BFF도 겸업하고 있었고, 모노레포는 그 역할을 독립적으로 나눈 것이다. 새로운 개념이 추가된 게 아니라, 이미 있던 걸 정리한 거였다.
잠깐, 서버가 토큰을 관리한다고?
레거시 Express가 BFF 역할을 하고 있다는 건 알겠는데, 하나 충격적인 부분이 있었다.
Express가 인증 쿠키/토큰을 직접 관리하고 있었다.
나는 당연히 토큰은 프론트엔드 상태관리(Pinia, Vuex)에서 관리하는 거라고 생각했다. 프록시 서버를 쓸 때도 토큰은 늘 프론트 몫이었으니까.
그런데 BFF 방식은 달랐다.
프록시 / 직접 호출 (토큰이 프론트에 있음):
로그인 → 백엔드가 토큰 반환 → Pinia/Vuex에 저장 → 매 요청마다 헤더에 붙임
- 토큰이 JavaScript 메모리에 있음
- XSS 공격 한 방이면 털릴 수 있음
- 개발자도구에서 토큰이 바로 보임
BFF (토큰이 서버에 있음):
로그인 → BFF가 백엔드에서 토큰 받음 → httpOnly 쿠키로 브라우저에 내려줌
- httpOnly 쿠키라 JavaScript로 접근 자체가 불가
- 브라우저가 자동으로 쿠키를 보내니까 프론트가 토큰을 만질 필요 없음
document.cookie로도 꺼낼 수 없음
BFF가 브라우저와 백엔드 사이에 껴있는 중간 서버이기 때문에 가능한 것이다. 프록시는 전달만 하니까 토큰을 관리할 이유가 없지만, BFF는 프론트를 위한 "전용 백엔드"이니까 토큰까지 책임지는 것이다.
그러면 상태관리는 뭘 하는 거야?
여기서 또 의문이 생겼다. BFF가 토큰을 관리하면, 상태관리 라이브러리는 뭘 하는 거지? 쓸모가 없어지는 건가?
아니다. 상태관리가 잘못된 게 아니라 뭘 담느냐의 문제였다.
| 담아도 되는 것 | 담으면 위험한 것 |
|---|---|
| 로그인한 유저 이름, 프로필 | 인증 토큰 (access/refresh token) |
| UI 상태 (사이드바, 다크모드) | API 키 |
| 서버에서 받아온 데이터 캐싱 | 민감한 개인정보 |
| 필터/검색 조건, 페이지네이션 |
역할 분담은 이렇게 된다:
보안 민감한 것 (토큰, 키) → BFF 서버 (httpOnly 쿠키)
화면 상태 + 공유 데이터 → 상태관리 (Pinia, Vuex)
일회성 데이터 → 컴포넌트 로컬 state (ref, reactive)
예를 들어, BFF에서 유저 정보를 한번 받아오면 상태관리에 캐싱한다.
이후에는 API를 다시 호출하지 않고 상태관리에서 바로 꺼내 쓰면 된다.
토큰은 BFF가 쿠키로 들고 있고, 토큰으로 가져온 데이터는 상태관리가 들고 있는 것이다.
보안이 필요한 건 서버가, 화면에 필요한 건 프론트가 — 각자의 영역에서 각자의 역할을 하는 거였다.
정리
- 프록시 서버는 요청을 전달만 한다. CORS와 API 은닉은 해결되지만, 토큰은 프론트가 관리해야 한다.
- BFF는 프록시에서 한 단계 진화한 것이다. 데이터 가공, API 조합, 토큰 관리까지 서버에서 처리한다.
- 레거시 Express 풀스택도 알고 보니 BFF 역할을 겸하고 있었다. 모노레포는 이걸 독립적으로 분리한 것이다.
- 상태관리는 보안 금고가 아니라 여러 컴포넌트가 같이 보는 칠판이다. 토큰 같은 민감한 건 BFF에, 화면 데이터는 상태관리에 두는 게 맞다.
'Dev > Backend' 카테고리의 다른 글
| [BFF/모노레포] 모노레포 환경에서의 BFF 구조와 pnpm (0) | 2026.04.03 |
|---|---|
| EJS 템플릿 엔진이란? (0) | 2026.03.03 |