{ "version": "1.1", "id": "ap3-bff-architecture", "title": "AP3는 browser session을 BFF-owned Bearer 요청으로 바꾼다", "question": "AP3에서 OAuth token 없는 브라우저 요청은 어느 경계를 지나 내부 Resource Server Bearer 요청이 되는가?", "type": "architecture", "direction": "LR", "audience": [ "BFF session과 downstream OAuth 경계를 설계하는 개발자" ], "summary": "브라우저에는 AP3_SESSION만 남고 BFF가 server-side authorized client에서 access token을 조회해 내부 Resource Server용 Bearer 요청을 만든다.", "alt": "Browser session zone과 server-side BFF zone 사이에서 AP3_SESSION이 downstream Bearer 요청으로 바뀌는 BFF 아키텍처.", "long_description": "왼쪽 browser session zone에는 OAuth token 없이 HttpOnly AP3_SESSION을 가진 브라우저가 있다. 오른쪽 server-side application zone에는 BFF, authorized-client store, 내부 Resource Server가 있다. 브라우저의 /bff/api/me 요청은 BFF에서 종료되고, BFF는 current authorized client를 조회해 server-held access token으로 Resource Server용 Bearer 요청을 조립한다.", "source_context": { "document": "document.md", "document_sha256": "e2cc061f683294c937f089ad874cbf7a617f7313c07a92c2148fb11287a0da37", "anchor": { "kind": "heading", "value": "AP3: browser token 비노출과 application-owned session을 맞바꾼다", "line": 190 } }, "composition": { "profile": "component-flow", "diagram_only": true, "reference_ids": [ "payment-event-flow" ], "rationale": "경계는 group 으로 남기고 방향이 있는 경로로 그린다 — two-zone-pipeline 은 지역 안쪽 엣지 라벨을 캔버스 위쪽에 고정해 group 테두리를 덮는다.", "focus_node": "bff" }, "groups": [ { "id": "browser-session-zone", "label": "Browser session boundary", "kind": "system", "role": "zone", "description": "OAuth token 대신 HttpOnly AP3_SESSION을 보유하는 browser 경계.", "evidence": [ { "start_line": 192, "end_line": 194 } ], "assumption": false }, { "id": "server-application-zone", "label": "Server-side application path", "kind": "system", "role": "zone", "description": "BFF, server-side authorized client와 내부 Resource Server가 연결되는 backend 경계.", "evidence": [ { "start_line": 192, "end_line": 196 } ], "assumption": false } ], "nodes": [ { "id": "browser", "label": "Browser", "kind": "actor", "role": "source", "group": "browser-session-zone", "shape": "actor", "description": "OAuth token을 받지 않고 AP3_SESSION으로 BFF endpoint를 호출한다.", "details": [ "HttpOnly AP3_SESSION", "OAuth token 없음" ], "evidence": [ { "start_line": 192, "end_line": 194 } ], "assumption": false }, { "id": "bff", "label": "Spring BFF", "kind": "service", "role": "service", "group": "server-application-zone", "emphasis": "primary", "description": "브라우저 session을 현재 authorized client와 downstream Bearer 요청으로 변환하는 confidential client.", "details": [ "bff-confidential", "oauth2Login", "/bff/api/me" ], "evidence": [ { "start_line": 192, "end_line": 196 } ], "assumption": false }, { "id": "authorized-client-store", "label": "Authorized-client store", "kind": "database", "role": "store", "group": "server-application-zone", "shape": "box", "description": "BFF가 access와 refresh token을 보관하고 현재 authorized client를 조회하는 server-side state.", "details": [ "access token", "refresh token" ], "evidence": [ { "start_line": 192, "end_line": 196 } ], "assumption": false }, { "id": "resource-server", "label": "Internal Resource Server", "kind": "service", "role": "sink", "group": "server-application-zone", "description": "BFF가 만든 Bearer 요청을 받는 내부 보호 자원.", "evidence": [ { "start_line": 194, "end_line": 194 } ], "assumption": false } ], "edges": [ { "id": "browser-to-bff", "from": "browser", "to": "bff", "label": "AP3_SESSION", "kind": "request", "evidence": [ { "start_line": 194, "end_line": 194 } ], "assumption": false }, { "id": "bff-to-store", "from": "bff", "to": "authorized-client-store", "label": "client 조회", "kind": "request", "evidence": [ { "start_line": 194, "end_line": 194 } ], "assumption": false }, { "id": "bff-to-resource-server", "from": "bff", "to": "resource-server", "label": "Bearer 요청", "kind": "request", "emphasis": "primary", "evidence": [ { "start_line": 194, "end_line": 194 } ], "assumption": false } ], "legend": [], "metadata": { "rationale": "Browser-to-API direct edge를 제거하고 BFF를 유일한 downstream Bearer caller로 표현했다." } }