Gateway Org Path 동작 이해하기
LinkPie가 browserAuthMode=GATEWAY로 동작할 때는 URL의 /org/{slug}가 단순 장식이 아닙니다.
운영 Gateway가 이 path를 보고 aipgw-ctx-* header를 붙이므로, LinkPie는 페이지 이동과 보호 API 호출에서 org slug를 잃지 않아야 합니다.
이 문서에서는 아래 역할명을 기준으로 설명합니다.
LinkPie Browser App: 브라우저 안에서 동작하는 LinkPie 프런트엔드AIP Gateway: 앞단 인증 게이트웨이Org Path Canonicalization Filter:/org/{slug}/api/**를/api/**로 되돌리는 backend filterGateway Auth Bridge:aipgw-ctx-*를 읽어 AIP gateway context를 gateway user principal로 연결하는 backend 인증 체인LinkPie API: 실제 backend API/controller 계층
왜 필요한가
- 운영 Gateway는
org path가 있는 요청에만 인증 header를 붙입니다. linkpie만 Gateway 모드를 사용하고,app과deskpie는 기존 local path를 유지합니다.- Edge/Gateway는 브라우저 내부에서 만들어지는 링크나 API URL을 고칠 수 없습니다.
그래서 LinkPie는 브라우저 안에서 URL을 만들 때 직접 /org/{slug}를 관리하고, backend는 받은 /org/{slug}/api/**를 다시 원래 /api/**로 되돌립니다.
한눈에 보는 흐름
1. 최초 페이지 로드
2. 내부 이동 + 보호 API 호출
누가 무엇을 담당하나
| 역할명 | 실제 클래스/파일 | 설명 |
|---|---|---|
| AIP Gateway Org Path Context | createAipGatewayOrgPathContext | 현재 브라우저 URL에서 orgSlug, 외부 path, canonical path를 계산합니다. |
| AIP Gateway Org Path Router | createAipGatewayOrgPathRouter | page/API path를 public인지 protected인지 분류하고, 외부 path와 내부 path를 서로 변환합니다. |
| AIP Gateway Org Path Routing Registration | registerAipGatewayOrgPathRouting | LinkPie 시작 시 route interceptor와 API request interceptor를 등록합니다. |
| Route Interceptor Seam | routeInterceptors | page ingress/egress를 위한 공용 seam입니다. app은 여기까지만 알고 org 규칙은 모릅니다. |
| API Interceptor Seam | apiInterceptors | HTTP client request를 전송 직전에 재작성하는 공용 seam입니다. |
| Org Path Canonicalization Filter | AipGatewayOrgPathCanonicalizationFilter | /org/{slug}/api/** 요청을 backend 내부 canonical /api/**로 되돌립니다. |
| AIP Gateway Context Resolver | AipGatewayContextResolver | aipgw-ctx-* header를 읽어 AIP gateway context를 만듭니다. |
| Gateway Auth Bridge Filter | AipGatewayAuthBridgeFilter | 보호 API 요청에서 AIP gateway context가 있을 때만 인증 bridge를 태웁니다. |
| Gateway User Details Service | AipGatewayUserDetailsService | gateway 외부 사용자 정보를 gateway user principal로 복원합니다. |
클래스 기준으로 보면
Frontend
Backend
위 순서도에서 쓴 이름은 이해를 돕기 위한 역할명이고, 실제 클래스명은 바로 위 표를 기준으로 보면 됩니다.
프런트는 왜 interceptor 방식인가
프런트는 공용 코드와 LinkPie 전용 정책을 분리해야 했습니다.
app은org slug를 몰라야 합니다.linkpie만/org/{slug}규칙을 알아야 합니다.navigate(),pushUrl(),replaceUrl(),http.get()가 모두 같은 정책을 타야 합니다.
그래서 공용 계층에는 interceptor seam만 두고, LinkPie가 시작 시 자기 router를 등록하는 구조로 정리했습니다.
백엔드는 왜 filter 방식인가
백엔드는 controller보다 앞에서 path를 바꿔야 했습니다.
- controller/service는 계속
/api/v1/**만 알게 두고 싶었습니다. - security matcher도 canonical API path를 기준으로 유지하고 싶었습니다.
그래서 gateway mode일 때만 pre-security filter를 등록해서 /org/{slug}/api/**를 /api/**로 canonicalize합니다.
OAuth calendar 연결은 예외가 아니라 포함 범위다
calendar 연결은 gateway mode에서도 계속 동작해야 합니다.
다만 이 흐름은 보호 API와 공용 OAuth 경로가 섞여 있어서 구분해서 봐야 합니다.
- OAuth 시작/콜백 경로는 public path로 그대로 둡니다.
- 예:
/oauth2/**
- 예:
- OAuth intent API는 인증된 보호 API로 다룹니다.
- 예:
/api/v1/auth/oauth2/intent
- 예:
- 대신 OAuth provider 시작/콜백 자체는 public path로 유지하고, 돌아올 위치는 backend continue cookie가 보존합니다.
즉 calendar OAuth는 "org path를 안 써도 되는 예외"가 아니라,
"공용 OAuth 경로는 그대로 두되, 보호 API와 복귀 위치 보존은 org path 체인을 유지해야 하는 흐름"으로 이해하면 됩니다.
실제 순서
1. 최초 진입
- 사용자가
/org/acme/console/contacts로 들어옵니다. - LinkPie는 현재 URL에서
orgSlug=acme를 읽습니다. - 내부에서는
/console/contacts를 canonical path로 사용합니다.
2. 내부 페이지 이동
- 코드가
/settings/account/profile같은 canonical page path로 이동합니다. - route egress interceptor가 이를
/org/acme/settings/account/profile로 바꿉니다. - 브라우저 URL은 계속 org slug를 유지합니다.
3. 보호 API 호출
- 코드가 canonical API path를 사용해 요청을 만듭니다.
- API request interceptor가 이를
/org/acme/api/v1/...로 바꿉니다. - Gateway가 path를 보고
aipgw-ctx-*header를 붙입니다. - backend filter가 다시
/api/v1/...로 되돌립니다. - 인증 bridge가 header를 읽어 사용자 principal을 복원합니다.
4. OAuth calendar 연결
- 사용자가
/org/acme/console/calendar-integrations같은 org-scoped page에 있습니다. - 보호 API가 현재 요청의 org-scoped path를 기준으로 backend continue cookie를 저장합니다.
- OAuth 시작은 public path로 나갑니다.
- provider 인증이 끝나면 callback도 public path로 돌아옵니다.
- 완료 후 다시
/org/acme/...형태의 원래 화면으로 복귀합니다.
현재 구현 상태
이미 전역으로 들어간 것
- protected page의
/org/{slug}유지 - backend의
/org/{slug}/api/**수용과 canonicalization - frontend protected API 기본 org-prefix
- raw
fetch()와 MCP fallback의 helper 연결
확인할 때 보면 좋은 포인트
- URL이
/org/{slug}를 유지한 채 페이지 이동하는지 - 보호 API가
/org/{slug}/api/v1/**로 나가는지 /api/v1/auth/config,/api/v1/auth/password-change-context같은 공용 API는 그대로 남는지/api/v1/auth/me,/api/v1/auth/oauth2/intent같은 보호 API는/org/{slug}/api/**로 나가는지- backend가
/org/{slug}/api/**를 받아도 controller는 기존/api/**로 처리하는지
로컬 gateway-mock은 편의상 slug 없이도 header를 붙일 수 있습니다.
로컬에서 slug 없이 동작했다고 해서 운영 Gateway도 같다고 보면 안 됩니다.
더 자세한 기술 문서
docs/reference/aip-gateway-org-path-routing.mddocs/reference/backend/gateway-session-bridge.md