- 계약 채택 — 독자 질문, 후보 29건(PROMOTE 24 · MERGE_INTO 4 · KEEP_IN_SSOT 1). 게시 중 17건은 전부 유지. 저장소 keycloak-pattern 은 패턴 넷이 브랜치로 갈라져 있어 revisions 로 tip 넷을 적었다. keycloak-session-store 는 같은 저장소 @ cdac9b8 - 게시된 기록의 redirect_uri 가 SSOT·코드와 달랐다 — OAuth2callback.html → callback.html (frontend/src/app.js 에서 확인). 계약 title 이 기록과 다른 7건도 기록 쪽으로 맞췄다 - 미작성 1건 작성 — 패턴 검증을 실제로 돌릴 때의 안전한 순서(Reference) - 리뷰 100건 반영 — 설명 뒤에 붙은 평가·차례 예고·독자 오해 가정·작성 지시를 지웠다. 삭제가 남긴 조각 4건을 고치고, 원래부터 잘려 있던 로컬 미리보기 라벨 1건도 닫았다 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
66 lines
4.2 KiB
Markdown
66 lines
4.2 KiB
Markdown
---
|
|
id: 19b55c39-c583-4161-9775-df954280a568
|
|
kind: PROJECT_DECISION
|
|
slug: bff-owns-token-when-browser-must-not
|
|
title: BFF가 OAuth Token을 관리하는 조건
|
|
topic: oauth-oidc-auth-boundary
|
|
topicName: OAuth/OIDC 인증 경계
|
|
project: KeyCloak Patterns
|
|
status: 게시 중
|
|
version: 23
|
|
decisionStatus: PROPOSED
|
|
decidedOn: 2026-08-31
|
|
studio: "https://hyeonworks.com/studio/documents/19b55c39-c583-4161-9775-df954280a568/edit"
|
|
public: "https://hyeonworks.com/projects/keycloak-patterns/decisions/bff-owns-token-when-browser-must-not"
|
|
sourceRevision: keycloak-patterns-lab@2026-08
|
|
source:
|
|
- final/document.md#선택의-이유와-지킨-경계-ap3
|
|
- final/document.md#얻은-것-잃은-것-적용하지-않을-때-ap3
|
|
---
|
|
|
|
# BFF가 OAuth Token을 관리하는 조건
|
|
|
|
BFF(Backend for Frontend)는 화면에 필요한 API를 브라우저 대신 호출하고 결과만 돌려주는 백엔드다. 애플리케이션 계층에서 API 응답 조합과 인가를 처리하면서도 브라우저 JavaScript에는 OAuth 토큰을 노출하지 않아야 한다면 이 구조를 선택할 수 있다.
|
|
|
|
이 경우 BFF가 authorization code를 토큰으로 교환해 액세스 토큰과 리프레시 토큰을 서버에 보관한다.
|
|
브라우저는 OAuth 토큰 대신 애플리케이션 세션으로 BFF를 호출하고,
|
|
BFF는 보관한 액세스 토큰으로 downstream Resource Server를 호출한다.
|
|
|
|
## 근거
|
|
|
|
- **Browser Token을 없애면서 BFF에 Session과 CSRF 책임이 생긴 과정**
|
|
이 결정대로 만든 구조를 실제로 실행해 본 기록이다.
|
|
- **BFF 인증 구조 설계 기준**
|
|
이 결정이 PROPOSED인 동안 실제로 따르는 기준이다.
|
|
- **OAuth/OIDC 인증 패턴 선택 기준**
|
|
이 결정을 적용할 조건과 피해야 할 조건을 갈라 놓은 기록이다.
|
|
- **Refresh Token 관리만 서버로 이전, Access Token은 여전히 Browser에 노출**
|
|
액세스 토큰이 브라우저로 나가서 이 요구를 만족하지 못한 경우다.
|
|
|
|
## 결정문
|
|
|
|
브라우저에 OAuth 토큰을 노출하지 않으면서 애플리케이션이 Resource Server 호출을 중계하고 조합해야 하는 경우, BFF가 authorization code 교환과 토큰 보관, downstream API 호출을 소유한다.
|
|
|
|
브라우저에는 애플리케이션 세션만 제공한다.
|
|
|
|
## 판단 이유
|
|
|
|
브라우저에 OAuth 토큰을 전달하지 않으려면 서버가 authorization code를 교환하고 액세스 토큰으로 downstream API를 호출해야 한다.
|
|
|
|
Mediator 구조에서는 브라우저가 Resource Server를 직접 호출하다 보니 액세스 토큰을 /token/access 응답으로 내보내고,
|
|
그래서 브라우저에 OAuth 토큰을 주지 않는다는 요구에는 맞지 않는다.
|
|
|
|
Forward-Auth 구조도 브라우저에 OAuth 토큰을 전달하지 않을 수 있지만, upstream은 JWT를 직접 검증하지 않고 edge가 넘겨준 identity header를 사용한다.
|
|
애플리케이션이 액세스 토큰으로 여러 Resource Server를 직접 호출하거나 사용자별 API 조합을 처리해야 한다면 BFF 쪽이 요구에 더 잘 맞는다.
|
|
|
|
처리량과 장애 복구 시간, session failover, secret rotation 절차는 확인하지 못해서 이 판단의 근거가 아니다.
|
|
|
|
## 영향
|
|
|
|
- BFF가 로그인 상태와 액세스 토큰, 리프레시 토큰을 보관하는 보안 구성요소가 되므로, 요청을 그대로 넘기는 프록시처럼 다루지 않는다.
|
|
- 상태를 바꾸는 요청마다 CSRF 검증이 붙는다. 클라이언트에 내려가는 값과 실제로 제출해야 하는 값이 다를 수 있어서 클라이언트 코드도 그 차이를 알아야 한다.
|
|
- 재시작과 replica 이동을 견딜 공유 저장소, 저장한 토큰의 암호화, 암호화 키 교체를 함께 설계해야 한다. 이 세 가지는 아직 정하지 못했다.
|
|
- 로그아웃은 애플리케이션 세션과 authorized client를 함께 지워야 하는데, 두 상태를 따로 보관하다 보니 한 번의 삭제로 둘이 같이 지워지지 않는다.
|
|
- 모든 UI 요청이 BFF를 지나므로 지연과 단일 장애 지점을 함께 준비해야 한다.
|
|
- 브라우저에서 토큰을 없애도 XSS는 막히지 않는다. 같은 origin에서 실행되는 악성 script는 사용자 세션으로 BFF를 호출할 수 있어서 XSS 방어는 따로 세워야 한다.
|