Files
document-haness/docs/keycloak/tech-log-studio/oauth-oidc-auth-boundary/reference/reference-bff-auth-design.md
T

9.5 KiB

id, kind, slug, title, topic, project, status, version, verifiedOn, studio, public
id kind slug title topic project status version verifiedOn studio public
97eddd97-1096-426a-a2c6-a6c5bf1cd09f REFERENCE bff-authentication-design-criteria BFF 인증 구조 설계 기준 OAuth/OIDC 인증 경계 KeyCloak Patterns 게시 중 21 2026-08-30 https://hyeonworks.com/studio/documents/97eddd97-1096-426a-a2c6-a6c5bf1cd09f/edit https://hyeonworks.com/references/bff-authentication-design-criteria

BFF 인증 구조 설계 기준

BFF 구조에서는 OAuth Token을 서버에서 관리하고, 브라우저는 Token 대신 Session Cookie를 사용해 BFF에 요청한다.

Cookie를 이용한 요청을 보호하기 위한 CSRF 검증, OAuth Token을 보관할 Authorized Client 저장소, 로그아웃할 때 Session과 Token을 함께 정리하는 방법, 그리고 BFF가 호출한 Resource Server에서 오류가 발생했을 때 이를 브라우저에 어떻게 전달할지를 같이 설계해야 한다.

관계

  • Browser Token을 없애면서 BFF에 Session과 CSRF 책임이 생긴 과정 이 기준의 항목 중 실제로 구현된 것과 비어 있는 것을 센 기록이다.
  • 서버 세션 기반 인증 구조는 다중 인스턴스에서 어떻게 운영할 것인가 저장소 항목이 아직 답이 없는 질문으로 남아 있다.
  • BFF의 Session과 OAuth2AuthorizedClient를 어디에 저장할 것인가 어느 저장소에 둘지가 이 기준의 미결 항목이다.
  • BFF가 OAuth Token을 관리하는 조건 이 결정이 PROPOSED인 동안 실제 적용 기준은 이 문서다.

목적

BFF 구조에서는 BFF가 authorization code를 token으로 교환하고, access token을 사용해 Resource Server를 호출한다. 따라서 session과 authorized client를 함께 관리해야 한다.

cookie가 credential이 되면 브라우저가 요청마다 자동으로 붙여 보낸다. 그래서 값을 바꾸는 요청은 사용자의 의도인지 따로 확인해야 한다. 재시작과 replica 이동을 견딜 저장소도 같이 필요하다.

규칙

1. 브라우저에는 OAuth token을 전달하지 않는다

Access Token과 Refresh Token은 BFF 서버의 Authorized Client에 보관한다. 브라우저는 OAuth Token을 직접 사용하지 않고 Session Cookie를 이용해 BFF에 요청한다.

BFF는 이 Session을 확인한 뒤, 저장해 둔 Access Token으로 Authorization: Bearer ... 헤더를 새로 만들어 Resource Server를 호출한다. 브라우저가 보낸 Session Cookie는 Resource Server로 전달되지 않는다. Session Cookie는 브라우저와 BFF 사이의 Credential이고, Access Token은 BFF와 Resource Server 사이의 Credential이다.

2. cookie가 credential이면 상태 변경 요청에 CSRF 검증을 둔다

BFF 구조에서는 브라우저가 요청할 때 Session Cookie를 자동으로 전송한다. 그래서 데이터 생성, 수정, 삭제처럼 서버의 상태를 변경하는 요청에는 해당 요청이 실제 사용자의 의도에 의해 만들어졌는지 확인하기 위한 CSRF 검증이 필요하다.

현재 구성에서는 서버가 CSRF Token을 Cookie로 전달하고, JavaScript가 그 값을 읽어 요청 Header에 다시 담아 보낸다. 서버는 Cookie와 Header를 함께 확인해 요청을 검증한다.

이때 화면이나 응답 본문에 표시되는 CSRF Token과 실제 요청 Header에 넣어야 하는 값이 항상 같다고 생각하면 안 된다. 응답 본문에 노출된 값이 별도의 처리를 거친 값이라면, 클라이언트는 실제 CSRF Cookie에서 값을 읽어 Header에 넣어야 한다. 잘못된 값을 보내면 정상적인 요청이라도 CSRF 검증에 실패해 403 Forbidden 응답을 받게 된다.

SameSite와 CSRF Token도 서로 다른 역할을 한다. SameSite는 브라우저가 Cross-Site 요청에 Cookie를 전송할지 제한하는 정책이고, CSRF Token은 Cookie가 포함되어 들어온 상태 변경 요청이 정상적인 클라이언트에서 만들어졌는지를 확인하기 위한 값이다. 또한 SameSite는 Origin이 아니라 Site를 기준으로 판단하므로, Origin은 다르지만 같은 Site에 속하는 요청도 존재할 수 있다.

3. session과 authorized client의 수명주기를 따로 설계한다

Application Session과 Authorized Client는 서로 다른 값을 저장하고 조회한다. Session은 session ID를 기준으로 조회하지만, Authorized Client는 client registration 이름principal name을 기준으로 조회한다. 따라서 여러 인스턴스에서 상태를 공유하기 위해 Shared Store를 도입할 때도 Session 저장소와 Authorized Client 저장소를 각각 어떻게 구성할지 확인해야 한다.

특히 Authorized Client의 조회 기준에는 session ID가 포함되지 않는다. 그래서 같은 사용자가 두 브라우저에서 동일한 Client로 로그인하면 두 Session이 같은 Authorized Client 정보를 사용하거나, 나중에 로그인하면서 저장된 Token 정보가 갱신될 수 있다. 브라우저나 Session마다 서로 다른 Token을 유지해야 한다면 session ID까지 포함해 Token을 구분할 수 있도록 별도의 저장 구조를 설계해야 한다.

운영 환경에서는 서버가 재시작되거나 요청이 다른 Replica로 전달되더라도 로그인 상태와 Token을 계속 사용할 수 있는지도 고려해야 한다. 이를 위해 Session과 Authorized Client를 공유 저장소에 보관할지, Session Affinity를 사용할지 등을 결정해야 한다. Token을 외부 저장소에 보관한다면 Access Token과 Refresh Token을 어떻게 보호할지도 정해야 하며, 저장 시 암호화한다면 암호화 Key의 보관 위치와 교체 방법까지 함께 설계해야 한다.

Logout에서도 두 상태를 각각 정리해야 한다. Application Session을 삭제하는 것만으로 Authorized Client에 저장된 OAuth Token까지 자동으로 삭제된다고 생각하면 안 된다. Session과 Authorized Client는 조회 기준과 저장소가 다르므로, Logout 시 Session과 Authorized Client가 모두 제거되는지 각각 확인해야 한다.

4. Downstream 오류를 클라이언트 응답으로 변환한다

BFF가 Resource Server의 오류를 그대로 브라우저에 전달하면 화면에서는 오류의 원인을 일관되게 판단하기 힘들다. 예를 들어 Resource Server에서 401 Unauthorized가 발생했다면 Access Token이 만료되었거나 더 이상 유효하지 않은 상황인지 확인하고, 필요한 경우 Token 갱신이나 재로그인으로 연결해야 한다. 하지만 인증은 정상적으로 되었지만 해당 기능을 사용할 권한이 없어 403 Forbidden이 발생한 경우에는 권한 부족으로 처리해야 한다.

Resource Server가 응답하지 않거나 처리가 지연되는 경우도 별도의 규칙이 필요하다. 요청을 얼마 동안 기다릴지 Timeout을 정하고, 실패한 요청을 다시 시도할 수 있는 경우에는 Retry 정책을 적용한다. 반복적으로 장애가 발생하는 Resource Server에 계속 요청을 보내지 않도록 Circuit Breaker를 적용할지도 함께 결정한다.

모든 UI 요청이 BFF를 거치는 구조라면 이러한 오류 처리 규칙도 BFF에서 일관되게 적용하는 것이 좋다. 그렇지 않으면 같은 종류의 오류를 화면마다 서로 다른 방식으로 판단하고 처리하게 될 수 있다.

5. BFF에서도 XSS 방어는 별도로 필요하다

BFF 구조에서는 Access Token과 Refresh Token을 서버에 보관하므로 브라우저의 JavaScript가 OAuth Token 원문에 직접 접근하지 않도록 할 수 있다. 하지만 이것이 브라우저에서 실행되는 악성 JavaScript까지 막아 주는 것은 아니다.

같은 Origin에서 악성 Script가 실행되면 사용자의 Session을 이용해 BFF Endpoint를 호출할 수 있다. 현재처럼 JavaScript가 CSRF Cookie를 읽어 Header에 넣는 구조라면 악성 Script 역시 같은 방식으로 CSRF Token을 읽어 요청을 만들 수 있다.

BFF에서는 OAuth Token 원문이 브라우저 JavaScript에 직접 노출되지 않지만, XSS 자체를 방지하기 위한 CSP, Output Encoding 등의 보호 조치와 외부 Script 및 의존성을 안전하게 관리하는 방법은 별도로 적용해야 한다. 또한 악성 Script가 사용자의 Session을 이용해 BFF를 호출하더라도 허용된 작업만 수행할 수 있도록 애플리케이션의 인가 역시 각 요청에서 검증해야 한다.

적용 조건

  • 브라우저가 OAuth token을 받아서는 안 될 때
  • backend가 화면에 맞춰 여러 API를 조합해야 할 때
  • 로그인 상태를 애플리케이션이 소유해야 할 때
  • downstream API가 늘어나도 브라우저는 하나만 알게 하고 싶을 때

예외

  • stateless 직접 API 호출과 독립 client가 핵심이면 BFF 구조로 설계하지 않는다. server state와 단일 장애 지점만 늘어난다.
  • 브라우저의 직접 API 호출을 남겨야 하면 refresh credential만 서버로 분리하는 구조가 맞다.
  • server state를 둘 수 없는 환경이면 브라우저가 token을 직접 다루는 구조가 더 단순하다.

예시

  • 브라우저 요청에는 Authorization 헤더가 없고 session cookie만 있다
  • BFF가 authorized client에서 access token을 읽어 downstream Bearer 요청을 새로 만든다
  • CSRF 헤더가 없는 POST는 403이 되고 cookie의 raw 값을 헤더에 넣은 POST는 200이 된다
  • 응답 본문의 token은 가려진 값이고 헤더에 넣는 값은 cookie의 raw 값이다