{ "schema_version": 2, "document": "브라우저 토큰에서 엣지 세션까지: Keycloak 인증 패턴 네 가지의 경계 설계", "citation_style": "hidden", "reader_document_contains_internal_source_ids": false, "sections": [ { "section_id": "01-problem-scene", "intent": "problem_scene", "title": "코드보다 먼저 드러난 문제", "reader_question": "독자가 공감할 수 있는 구체적인 상황에서 어떤 문제가 드러났는가?", "decision_requirements": [], "evidence": [ { "id": "L4121b8d86b", "title": "four pattern tradeoff matrix — Four Keycloak integration patterns", "source_type": "local-document", "status": "", "path": "docs/four-pattern-tradeoff-matrix.md", "heading": "Four Keycloak integration patterns", "line_start": 1, "line_end": 14, "url": "repo:///docs/four-pattern-tradeoff-matrix.md", "accessed": "", "claim_ids": [], "decision_ids": [], "priority": 45.74042 }, { "id": "AP1_BOUNDARY", "title": "AP1 SPA direct의 OAuth·token 책임 경계", "source_type": "branch-note", "status": "reviewed", "path": "docs/internal-spa-direct-no-google.md", "heading": "AP1 internal SPA direct: local identity profile", "line_start": 3, "line_end": 10, "url": "repo://keycloak-pattern/develop-keycloak-pattern1/docs/internal-spa-direct-no-google.md", "accessed": "", "claim_ids": [ "AP1-C1" ], "decision_ids": [ "AP1-D1" ], "priority": 100.0 }, { "id": "AP2_BOUNDARY", "title": "AP2 confidential token mediator의 책임 경계", "source_type": "branch-note", "status": "reviewed", "path": "docs/ap2-token-boundary.md", "heading": "책임 경계", "line_start": 3, "line_end": 18, "url": "repo://keycloak-pattern/develop-keycloak-pattern2/docs/ap2-token-boundary.md", "accessed": "", "claim_ids": [ "AP2-C1" ], "decision_ids": [ "AP2-D1" ], "priority": 100.0 }, { "id": "AP3_BOUNDARY", "title": "AP3 BFF의 tokenless browser 경계", "source_type": "branch-note", "status": "reviewed", "path": "docs/ap3-bff-boundary.md", "heading": "요청과 token 경계", "line_start": 3, "line_end": 18, "url": "repo://keycloak-pattern/develop-keycloak-pattern3/docs/ap3-bff-boundary.md", "accessed": "", "claim_ids": [ "AP3-C1" ], "decision_ids": [ "AP3-D1" ], "priority": 100.0 }, { "id": "AP4_BOUNDARY", "title": "AP4 oauth2-proxy와 nginx edge 책임 경계", "source_type": "branch-note", "status": "reviewed", "path": "docs/ap4-edge-forward-auth.md", "heading": "AP4 oauth2-proxy Edge Forward Auth", "line_start": 3, "line_end": 70, "url": "repo://keycloak-pattern/develop-keycloak-pattern4/docs/ap4-edge-forward-auth.md", "accessed": "", "claim_ids": [ "AP4-C1" ], "decision_ids": [ "AP4-D1" ], "priority": 100.0 } ], "evidence_gap": false }, { "section_id": "02-constraints", "intent": "constraints", "title": "문제를 어렵게 만든 제약", "reader_question": "단순한 해법을 막은 프로젝트 제약은 무엇이었는가?", "decision_requirements": [], "evidence": [ { "id": "L4121b8d86b", "title": "four pattern tradeoff matrix — Four Keycloak integration patterns", "source_type": "local-document", "status": "", "path": "docs/four-pattern-tradeoff-matrix.md", "heading": "Four Keycloak integration patterns", "line_start": 1, "line_end": 14, "url": "repo:///docs/four-pattern-tradeoff-matrix.md", "accessed": "", "claim_ids": [], "decision_ids": [], "priority": 45.74042 }, { "id": "AP1_STORAGE", "title": "AP1 token 저장 선택과 수용 비용", "source_type": "branch-note", "status": "reviewed", "path": "docs/ap1-token-storage.md", "heading": "AP1 token storage trade-off", "line_start": 3, "line_end": 29, "url": "repo://keycloak-pattern/develop-keycloak-pattern1/docs/ap1-token-storage.md", "accessed": "", "claim_ids": [ "AP1-C2" ], "decision_ids": [ "AP1-D2" ], "priority": 100.0 }, { "id": "AP2_GUARDRAILS", "title": "AP2 session, refresh custody, CORS와 audience 가드레일", "source_type": "canonical-project", "status": "reviewed", "path": "token-mediator/src/main/resources/application.yml", "heading": "AP2 session and OAuth client configuration", "line_start": 3, "line_end": 34, "url": "repo://keycloak-pattern/develop-keycloak-pattern2/token-mediator/src/main/resources/application.yml", "accessed": "", "claim_ids": [ "AP2-C3" ], "decision_ids": [], "priority": 85.0 }, { "id": "AP3_TRADEOFF", "title": "AP3와 AP1의 위협 모델·운영비 교환", "source_type": "branch-note", "status": "reviewed", "path": "docs/bff-vs-spa-direct.md", "heading": "BFF vs SPA direct", "line_start": 3, "line_end": 22, "url": "repo://keycloak-pattern/develop-keycloak-pattern3/docs/bff-vs-spa-direct.md", "accessed": "", "claim_ids": [ "AP3-C2" ], "decision_ids": [ "AP3-D2" ], "priority": 100.0 }, { "id": "AP4_BOUNDARY", "title": "AP4 oauth2-proxy와 nginx edge 책임 경계", "source_type": "branch-note", "status": "reviewed", "path": "docs/ap4-edge-forward-auth.md", "heading": "AP4 oauth2-proxy Edge Forward Auth", "line_start": 3, "line_end": 70, "url": "repo://keycloak-pattern/develop-keycloak-pattern4/docs/ap4-edge-forward-auth.md", "accessed": "", "claim_ids": [ "AP4-C1" ], "decision_ids": [ "AP4-D1" ], "priority": 100.0 }, { "id": "AP4_RESPONSE_RUNTIME", "title": "AP4 trusted header input에서 edge identity JSON까지", "source_type": "canonical-project", "status": "reviewed", "path": "backend/src/main/java/com/example/keycloakpattern/EdgeIdentityController.java", "heading": "currentUser and hasValidInternalToken", "line_start": 27, "line_end": 53, "url": "repo://keycloak-pattern/develop-keycloak-pattern4/backend/src/main/java/com/example/keycloakpattern/EdgeIdentityController.java", "accessed": "", "claim_ids": [ "AP4-C7" ], "decision_ids": [], "priority": 100.0 } ], "evidence_gap": false }, { "section_id": "03-options", "intent": "options", "title": "검토한 선택지와 막힌 지점", "reader_question": "어떤 대안들을 검토했고 각각 어디에서 비용이 생겼는가?", "decision_requirements": [ "상황·제약", "선택", "선택 이유", "검토한 대안", "수용한 비용", "보완 가드레일" ], "evidence": [ { "id": "L4121b8d86b", "title": "four pattern tradeoff matrix — Four Keycloak integration patterns", "source_type": "local-document", "status": "", "path": "docs/four-pattern-tradeoff-matrix.md", "heading": "Four Keycloak integration patterns", "line_start": 1, "line_end": 14, "url": "repo:///docs/four-pattern-tradeoff-matrix.md", "accessed": "", "claim_ids": [], "decision_ids": [], "priority": 45.74042 }, { "id": "AP1_BOUNDARY", "title": "AP1 SPA direct의 OAuth·token 책임 경계", "source_type": "branch-note", "status": "reviewed", "path": "docs/internal-spa-direct-no-google.md", "heading": "AP1 internal SPA direct: local identity profile", "line_start": 3, "line_end": 10, "url": "repo://keycloak-pattern/develop-keycloak-pattern1/docs/internal-spa-direct-no-google.md", "accessed": "", "claim_ids": [ "AP1-C1" ], "decision_ids": [ "AP1-D1" ], "priority": 100.0 }, { "id": "AP1_STORAGE", "title": "AP1 token 저장 선택과 수용 비용", "source_type": "branch-note", "status": "reviewed", "path": "docs/ap1-token-storage.md", "heading": "AP1 token storage trade-off", "line_start": 3, "line_end": 29, "url": "repo://keycloak-pattern/develop-keycloak-pattern1/docs/ap1-token-storage.md", "accessed": "", "claim_ids": [ "AP1-C2" ], "decision_ids": [ "AP1-D2" ], "priority": 100.0 }, { "id": "AP2_BOUNDARY", "title": "AP2 confidential token mediator의 책임 경계", "source_type": "branch-note", "status": "reviewed", "path": "docs/ap2-token-boundary.md", "heading": "책임 경계", "line_start": 3, "line_end": 18, "url": "repo://keycloak-pattern/develop-keycloak-pattern2/docs/ap2-token-boundary.md", "accessed": "", "claim_ids": [ "AP2-C1" ], "decision_ids": [ "AP2-D1" ], "priority": 100.0 }, { "id": "AP2_IMPLEMENTATION", "title": "AP2 access-only handoff의 실제 구현", "source_type": "canonical-project", "status": "reviewed-discrepancy", "path": "token-mediator/src/main/java/com/example/keycloakpattern/mediator/AccessTokenController.java", "heading": "accessToken", "line_start": 28, "line_end": 55, "url": "repo://keycloak-pattern/develop-keycloak-pattern2/token-mediator/src/main/java/com/example/keycloakpattern/mediator/AccessTokenController.java", "accessed": "", "claim_ids": [ "AP2-C2" ], "decision_ids": [], "priority": 100.0 }, { "id": "AP3_BOUNDARY", "title": "AP3 BFF의 tokenless browser 경계", "source_type": "branch-note", "status": "reviewed", "path": "docs/ap3-bff-boundary.md", "heading": "요청과 token 경계", "line_start": 3, "line_end": 18, "url": "repo://keycloak-pattern/develop-keycloak-pattern3/docs/ap3-bff-boundary.md", "accessed": "", "claim_ids": [ "AP3-C1" ], "decision_ids": [ "AP3-D1" ], "priority": 100.0 }, { "id": "AP3_TRADEOFF", "title": "AP3와 AP1의 위협 모델·운영비 교환", "source_type": "branch-note", "status": "reviewed", "path": "docs/bff-vs-spa-direct.md", "heading": "BFF vs SPA direct", "line_start": 3, "line_end": 22, "url": "repo://keycloak-pattern/develop-keycloak-pattern3/docs/bff-vs-spa-direct.md", "accessed": "", "claim_ids": [ "AP3-C2" ], "decision_ids": [ "AP3-D2" ], "priority": 100.0 }, { "id": "AP4_BOUNDARY", "title": "AP4 oauth2-proxy와 nginx edge 책임 경계", "source_type": "branch-note", "status": "reviewed", "path": "docs/ap4-edge-forward-auth.md", "heading": "AP4 oauth2-proxy Edge Forward Auth", "line_start": 3, "line_end": 70, "url": "repo://keycloak-pattern/develop-keycloak-pattern4/docs/ap4-edge-forward-auth.md", "accessed": "", "claim_ids": [ "AP4-C1" ], "decision_ids": [ "AP4-D1" ], "priority": 100.0 }, { "id": "AP4_ALTERNATIVE", "title": "AP4 Traefik ForwardAuth 대안과 추가 비용", "source_type": "branch-note", "status": "config-tested", "path": "docs/traefik-forwardauth-alternative.md", "heading": "Traefik ForwardAuth alternative", "line_start": 3, "line_end": 22, "url": "repo://keycloak-pattern/develop-keycloak-pattern4/docs/traefik-forwardauth-alternative.md", "accessed": "", "claim_ids": [ "AP4-C4" ], "decision_ids": [ "AP4-D2" ], "priority": 85.0 } ], "evidence_gap": false }, { "section_id": "04-decision-rationale", "intent": "decision_rationale", "title": "선택의 이유와 지킨 경계", "reader_question": "왜 이 선택을 했으며 무엇을 일부러 포기하거나 금지했는가?", "decision_requirements": [ "상황·제약", "선택", "선택 이유", "검토한 대안", "수용한 비용", "보완 가드레일" ], "evidence": [ { "id": "AP1_BOUNDARY", "title": "AP1 SPA direct의 OAuth·token 책임 경계", "source_type": "branch-note", "status": "reviewed", "path": "docs/internal-spa-direct-no-google.md", "heading": "AP1 internal SPA direct: local identity profile", "line_start": 3, "line_end": 10, "url": "repo://keycloak-pattern/develop-keycloak-pattern1/docs/internal-spa-direct-no-google.md", "accessed": "", "claim_ids": [ "AP1-C1" ], "decision_ids": [ "AP1-D1" ], "priority": 100.0 }, { "id": "AP1_STORAGE", "title": "AP1 token 저장 선택과 수용 비용", "source_type": "branch-note", "status": "reviewed", "path": "docs/ap1-token-storage.md", "heading": "AP1 token storage trade-off", "line_start": 3, "line_end": 29, "url": "repo://keycloak-pattern/develop-keycloak-pattern1/docs/ap1-token-storage.md", "accessed": "", "claim_ids": [ "AP1-C2" ], "decision_ids": [ "AP1-D2" ], "priority": 100.0 }, { "id": "AP2_BOUNDARY", "title": "AP2 confidential token mediator의 책임 경계", "source_type": "branch-note", "status": "reviewed", "path": "docs/ap2-token-boundary.md", "heading": "책임 경계", "line_start": 3, "line_end": 18, "url": "repo://keycloak-pattern/develop-keycloak-pattern2/docs/ap2-token-boundary.md", "accessed": "", "claim_ids": [ "AP2-C1" ], "decision_ids": [ "AP2-D1" ], "priority": 100.0 }, { "id": "AP3_BOUNDARY", "title": "AP3 BFF의 tokenless browser 경계", "source_type": "branch-note", "status": "reviewed", "path": "docs/ap3-bff-boundary.md", "heading": "요청과 token 경계", "line_start": 3, "line_end": 18, "url": "repo://keycloak-pattern/develop-keycloak-pattern3/docs/ap3-bff-boundary.md", "accessed": "", "claim_ids": [ "AP3-C1" ], "decision_ids": [ "AP3-D1" ], "priority": 100.0 }, { "id": "AP3_TRADEOFF", "title": "AP3와 AP1의 위협 모델·운영비 교환", "source_type": "branch-note", "status": "reviewed", "path": "docs/bff-vs-spa-direct.md", "heading": "BFF vs SPA direct", "line_start": 3, "line_end": 22, "url": "repo://keycloak-pattern/develop-keycloak-pattern3/docs/bff-vs-spa-direct.md", "accessed": "", "claim_ids": [ "AP3-C2" ], "decision_ids": [ "AP3-D2" ], "priority": 100.0 }, { "id": "AP4_BOUNDARY", "title": "AP4 oauth2-proxy와 nginx edge 책임 경계", "source_type": "branch-note", "status": "reviewed", "path": "docs/ap4-edge-forward-auth.md", "heading": "AP4 oauth2-proxy Edge Forward Auth", "line_start": 3, "line_end": 70, "url": "repo://keycloak-pattern/develop-keycloak-pattern4/docs/ap4-edge-forward-auth.md", "accessed": "", "claim_ids": [ "AP4-C1" ], "decision_ids": [ "AP4-D1" ], "priority": 100.0 }, { "id": "AP4_ALTERNATIVE", "title": "AP4 Traefik ForwardAuth 대안과 추가 비용", "source_type": "branch-note", "status": "config-tested", "path": "docs/traefik-forwardauth-alternative.md", "heading": "Traefik ForwardAuth alternative", "line_start": 3, "line_end": 22, "url": "repo://keycloak-pattern/develop-keycloak-pattern4/docs/traefik-forwardauth-alternative.md", "accessed": "", "claim_ids": [ "AP4-C4" ], "decision_ids": [ "AP4-D2" ], "priority": 85.0 } ], "evidence_gap": false }, { "section_id": "05-mechanism", "intent": "mechanism", "title": "선택이 코드와 흐름에 반영되는 방식", "reader_question": "결정이 모듈, 인터페이스, 제어 흐름에 어떻게 반영되는가?", "decision_requirements": [], "evidence": [ { "id": "AP1_LOGIN_RUNTIME", "title": "AP1 SPA authorization, callback와 browser token data flow", "source_type": "canonical-project", "status": "reviewed", "path": "frontend/src/app.js", "heading": "UserManager configuration, callback, and renderSession", "line_start": 9, "line_end": 81, "url": "repo://keycloak-pattern/develop-keycloak-pattern1/frontend/src/app.js", "accessed": "", "claim_ids": [ "AP1-C4" ], "decision_ids": [], "priority": 100.0 }, { "id": "AP1_PKCE_DEMO_GAP", "title": "AP1 manual PKCE helper와 실제 signin path의 구분", "source_type": "canonical-project", "status": "reviewed-gap", "path": "frontend/src/pkce.js", "heading": "createPkcePair", "line_start": 1, "line_end": 25, "url": "repo://keycloak-pattern/develop-keycloak-pattern1/frontend/src/pkce.js", "accessed": "", "claim_ids": [ "AP1-C5" ], "decision_ids": [], "priority": 85.0 }, { "id": "AP1_API_RUNTIME", "title": "AP1 browser Bearer input에서 /api/me JSON까지", "source_type": "canonical-project", "status": "reviewed", "path": "frontend/src/app.js", "heading": "callProtectedApi", "line_start": 83, "line_end": 109, "url": "repo://keycloak-pattern/develop-keycloak-pattern1/frontend/src/app.js", "accessed": "", "claim_ids": [ "AP1-C6" ], "decision_ids": [], "priority": 100.0 }, { "id": "AP1_ROLE_FAILURE_RUNTIME", "title": "AP1 JWT failure와 realm role authorization 경계", "source_type": "canonical-project", "status": "reviewed-gap", "path": "backend/src/main/java/com/example/keycloakpattern/KeycloakRealmRoleConverter.java", "heading": "convert", "line_start": 12, "line_end": 27, "url": "repo://keycloak-pattern/develop-keycloak-pattern1/backend/src/main/java/com/example/keycloakpattern/KeycloakRealmRoleConverter.java", "accessed": "", "claim_ids": [ "AP1-C7" ], "decision_ids": [], "priority": 90.0 }, { "id": "AP2_LOGIN_FLOW", "title": "AP2 browser entry와 Spring oauth2Login code 교환", "source_type": "canonical-project", "status": "reviewed", "path": "token-mediator/src/main/resources/static/app.js", "heading": "loginButton click and OAuth client registration", "line_start": 7, "line_end": 9, "url": "repo://keycloak-pattern/develop-keycloak-pattern2/token-mediator/src/main/resources/static/app.js", "accessed": "", "claim_ids": [ "AP2-C4" ], "decision_ids": [], "priority": 90.0 }, { "id": "AP2_BOUNDARY_RUNTIME", "title": "AP2 token boundary endpoint input과 output", "source_type": "canonical-project", "status": "reviewed", "path": "token-mediator/src/main/java/com/example/keycloakpattern/mediator/TokenBoundaryController.java", "heading": "tokenBoundary", "line_start": 25, "line_end": 42, "url": "repo://keycloak-pattern/develop-keycloak-pattern2/token-mediator/src/main/java/com/example/keycloakpattern/mediator/TokenBoundaryController.java", "accessed": "", "claim_ids": [ "AP2-C5" ], "decision_ids": [], "priority": 95.0 }, { "id": "AP2_ACCESS_RUNTIME", "title": "AP2 access handoff와 browser direct API의 data transformation", "source_type": "canonical-project", "status": "reviewed", "path": "token-mediator/src/main/java/com/example/keycloakpattern/mediator/AccessTokenController.java", "heading": "accessToken", "line_start": 28, "line_end": 54, "url": "repo://keycloak-pattern/develop-keycloak-pattern2/token-mediator/src/main/java/com/example/keycloakpattern/mediator/AccessTokenController.java", "accessed": "", "claim_ids": [ "AP2-C6" ], "decision_ids": [], "priority": 100.0 }, { "id": "AP2_RESOURCE_RUNTIME", "title": "AP2 Resource Server의 JWT input과 /api/me output", "source_type": "canonical-project", "status": "reviewed", "path": "backend/src/main/java/com/example/keycloakpattern/ApiController.java", "heading": "currentUser", "line_start": 21, "line_end": 28, "url": "repo://keycloak-pattern/develop-keycloak-pattern2/backend/src/main/java/com/example/keycloakpattern/ApiController.java", "accessed": "", "claim_ids": [ "AP2-C7" ], "decision_ids": [], "priority": 95.0 }, { "id": "AP3_LOGIN_FLOW", "title": "AP3 oauth2Login과 server-side PKCE data flow", "source_type": "canonical-project", "status": "reviewed", "path": "bff/src/main/java/com/example/keycloakpattern/bff/SecurityConfig.java", "heading": "authorizationRequestResolver and bffSecurity", "line_start": 20, "line_end": 58, "url": "repo://keycloak-pattern/develop-keycloak-pattern3/bff/src/main/java/com/example/keycloakpattern/bff/SecurityConfig.java", "accessed": "", "claim_ids": [ "AP3-C4" ], "decision_ids": [], "priority": 95.0 }, { "id": "AP3_BOUNDARY_RUNTIME", "title": "AP3 token boundary endpoint의 input과 관측 output", "source_type": "canonical-project", "status": "reviewed", "path": "bff/src/main/java/com/example/keycloakpattern/bff/BffController.java", "heading": "tokenBoundary", "line_start": 44, "line_end": 64, "url": "repo://keycloak-pattern/develop-keycloak-pattern3/bff/src/main/java/com/example/keycloakpattern/bff/BffController.java", "accessed": "", "claim_ids": [ "AP3-C5" ], "decision_ids": [], "priority": 95.0 }, { "id": "AP3_API_RUNTIME", "title": "AP3 session input에서 downstream Bearer와 reader JSON까지", "source_type": "canonical-project", "status": "reviewed", "path": "bff/src/main/java/com/example/keycloakpattern/bff/BffController.java", "heading": "currentUser and authorizedClient", "line_start": 67, "line_end": 111, "url": "repo://keycloak-pattern/develop-keycloak-pattern3/bff/src/main/java/com/example/keycloakpattern/bff/BffController.java", "accessed": "", "claim_ids": [ "AP3-C6" ], "decision_ids": [], "priority": 100.0 }, { "id": "AP3_CSRF_RUNTIME", "title": "AP3 CSRF cookie-to-header transformation과 preferences output", "source_type": "canonical-project", "status": "reviewed", "path": "bff/src/main/java/com/example/keycloakpattern/bff/CsrfController.java", "heading": "csrf", "line_start": 14, "line_end": 23, "url": "repo://keycloak-pattern/develop-keycloak-pattern3/bff/src/main/java/com/example/keycloakpattern/bff/CsrfController.java", "accessed": "", "claim_ids": [ "AP3-C7" ], "decision_ids": [], "priority": 100.0 }, { "id": "AP3_PREFERENCE_SCOPE", "title": "AP3 preferences 예시의 process-global state 간극", "source_type": "canonical-project", "status": "reviewed-gap", "path": "bff/src/main/java/com/example/keycloakpattern/bff/BffController.java", "heading": "preferenceTheme and updatePreferences", "line_start": 28, "line_end": 95, "url": "repo://keycloak-pattern/develop-keycloak-pattern3/bff/src/main/java/com/example/keycloakpattern/bff/BffController.java", "accessed": "", "claim_ids": [ "AP3-C8" ], "decision_ids": [], "priority": 90.0 }, { "id": "AP4_LOGIN_RUNTIME", "title": "AP4 unauthenticated navigation에서 oauth2-proxy session까지", "source_type": "canonical-project", "status": "reviewed", "path": "docker-compose.yml", "heading": "oauth2-proxy service", "line_start": 92, "line_end": 143, "url": "repo://keycloak-pattern/develop-keycloak-pattern4/docker-compose.yml", "accessed": "", "claim_ids": [ "AP4-C5" ], "decision_ids": [], "priority": 100.0 }, { "id": "AP4_REQUEST_RUNTIME", "title": "AP4 external request에서 auth subrequest와 upstream input까지", "source_type": "canonical-project", "status": "reviewed", "path": "frontend/default.conf.template", "heading": "auth_request and upstream mapping", "line_start": 13, "line_end": 74, "url": "repo://keycloak-pattern/develop-keycloak-pattern4/frontend/default.conf.template", "accessed": "", "claim_ids": [ "AP4-C6" ], "decision_ids": [], "priority": 100.0 }, { "id": "AP4_RESPONSE_RUNTIME", "title": "AP4 trusted header input에서 edge identity JSON까지", "source_type": "canonical-project", "status": "reviewed", "path": "backend/src/main/java/com/example/keycloakpattern/EdgeIdentityController.java", "heading": "currentUser and hasValidInternalToken", "line_start": 27, "line_end": 53, "url": "repo://keycloak-pattern/develop-keycloak-pattern4/backend/src/main/java/com/example/keycloakpattern/EdgeIdentityController.java", "accessed": "", "claim_ids": [ "AP4-C7" ], "decision_ids": [], "priority": 100.0 } ], "evidence_gap": false }, { "section_id": "06-evidence-verification", "intent": "evidence_verification", "title": "결정이 지켜지는지 확인하는 방법", "reader_question": "설명한 경계와 결과가 실제로 유지되는지 어떻게 확인하는가?", "decision_requirements": [], "evidence": [ { "id": "AP1_VERIFY", "title": "AP1 브라우저 흐름과 token 수명주기 검증 계약", "source_type": "canonical-project", "status": "test-defined", "path": "e2e/pattern1.mjs", "heading": "AP1 Playwright acceptance contract", "line_start": 82, "line_end": 202, "url": "repo://keycloak-pattern/develop-keycloak-pattern1/e2e/pattern1.mjs", "accessed": "", "claim_ids": [ "AP1-T1" ], "decision_ids": [], "priority": 80.0 }, { "id": "AP1_ROLE_FAILURE_RUNTIME", "title": "AP1 JWT failure와 realm role authorization 경계", "source_type": "canonical-project", "status": "reviewed-gap", "path": "backend/src/main/java/com/example/keycloakpattern/KeycloakRealmRoleConverter.java", "heading": "convert", "line_start": 12, "line_end": 27, "url": "repo://keycloak-pattern/develop-keycloak-pattern1/backend/src/main/java/com/example/keycloakpattern/KeycloakRealmRoleConverter.java", "accessed": "", "claim_ids": [ "AP1-C7" ], "decision_ids": [], "priority": 90.0 }, { "id": "AP2_VERIFY", "title": "AP2 access-only 전달과 브라우저 직접 API 호출 검증 계약", "source_type": "canonical-project", "status": "test-defined", "path": "e2e/pattern2.mjs", "heading": "AP2 Playwright acceptance contract", "line_start": 39, "line_end": 112, "url": "repo://keycloak-pattern/develop-keycloak-pattern2/e2e/pattern2.mjs", "accessed": "", "claim_ids": [ "AP2-T1" ], "decision_ids": [], "priority": 80.0 }, { "id": "AP2_ACCESS_RUNTIME", "title": "AP2 access handoff와 browser direct API의 data transformation", "source_type": "canonical-project", "status": "reviewed", "path": "token-mediator/src/main/java/com/example/keycloakpattern/mediator/AccessTokenController.java", "heading": "accessToken", "line_start": 28, "line_end": 54, "url": "repo://keycloak-pattern/develop-keycloak-pattern2/token-mediator/src/main/java/com/example/keycloakpattern/mediator/AccessTokenController.java", "accessed": "", "claim_ids": [ "AP2-C6" ], "decision_ids": [], "priority": 100.0 }, { "id": "AP3_VERIFY", "title": "AP3 browser token 비노출과 CSRF 방어 검증 계약", "source_type": "canonical-project", "status": "test-defined", "path": "e2e/pattern3.mjs", "heading": "AP3 Playwright acceptance contract", "line_start": 43, "line_end": 191, "url": "repo://keycloak-pattern/develop-keycloak-pattern3/e2e/pattern3.mjs", "accessed": "", "claim_ids": [ "AP3-T1" ], "decision_ids": [], "priority": 80.0 }, { "id": "AP3_CSRF_RUNTIME", "title": "AP3 CSRF cookie-to-header transformation과 preferences output", "source_type": "canonical-project", "status": "reviewed", "path": "bff/src/main/java/com/example/keycloakpattern/bff/CsrfController.java", "heading": "csrf", "line_start": 14, "line_end": 23, "url": "repo://keycloak-pattern/develop-keycloak-pattern3/bff/src/main/java/com/example/keycloakpattern/bff/CsrfController.java", "accessed": "", "claim_ids": [ "AP3-C7" ], "decision_ids": [], "priority": 100.0 }, { "id": "AP4_VERIFY", "title": "AP4 edge login과 header spoofing 방어 검증 계약", "source_type": "canonical-project", "status": "test-defined", "path": "e2e/pattern4.mjs", "heading": "AP4 Playwright acceptance contract", "line_start": 44, "line_end": 131, "url": "repo://keycloak-pattern/develop-keycloak-pattern4/e2e/pattern4.mjs", "accessed": "", "claim_ids": [ "AP4-T1" ], "decision_ids": [], "priority": 80.0 }, { "id": "AP4_RESPONSE_RUNTIME", "title": "AP4 trusted header input에서 edge identity JSON까지", "source_type": "canonical-project", "status": "reviewed", "path": "backend/src/main/java/com/example/keycloakpattern/EdgeIdentityController.java", "heading": "currentUser and hasValidInternalToken", "line_start": 27, "line_end": 53, "url": "repo://keycloak-pattern/develop-keycloak-pattern4/backend/src/main/java/com/example/keycloakpattern/EdgeIdentityController.java", "accessed": "", "claim_ids": [ "AP4-C7" ], "decision_ids": [], "priority": 100.0 }, { "id": "BRANCH_REACHABILITY", "title": "네 pattern branch와 39개 feature ref의 도달성", "source_type": "canonical-project", "status": "partially-verified", "path": "docs/keycloak-branch-manifest.tsv", "heading": "branch target delivery registry", "line_start": 1, "line_end": 40, "url": "repo://keycloak-pattern/develop/docs/keycloak-branch-manifest.tsv", "accessed": "", "claim_ids": [ "COMMON-T1" ], "decision_ids": [], "priority": 70.0 } ], "evidence_gap": false }, { "section_id": "07-tradeoffs", "intent": "tradeoffs", "title": "얻은 것, 잃은 것, 적용하지 않을 때", "reader_question": "이 선택의 비용과 한계는 무엇이며 언제 다른 선택이 나은가?", "decision_requirements": [ "상황·제약", "선택", "선택 이유", "검토한 대안", "수용한 비용", "보완 가드레일" ], "evidence": [ { "id": "L4121b8d86b", "title": "four pattern tradeoff matrix — Four Keycloak integration patterns", "source_type": "local-document", "status": "", "path": "docs/four-pattern-tradeoff-matrix.md", "heading": "Four Keycloak integration patterns", "line_start": 1, "line_end": 14, "url": "repo:///docs/four-pattern-tradeoff-matrix.md", "accessed": "", "claim_ids": [], "decision_ids": [], "priority": 45.74042 }, { "id": "AP1_STORAGE", "title": "AP1 token 저장 선택과 수용 비용", "source_type": "branch-note", "status": "reviewed", "path": "docs/ap1-token-storage.md", "heading": "AP1 token storage trade-off", "line_start": 3, "line_end": 29, "url": "repo://keycloak-pattern/develop-keycloak-pattern1/docs/ap1-token-storage.md", "accessed": "", "claim_ids": [ "AP1-C2" ], "decision_ids": [ "AP1-D2" ], "priority": 100.0 }, { "id": "AP2_GUARDRAILS", "title": "AP2 session, refresh custody, CORS와 audience 가드레일", "source_type": "canonical-project", "status": "reviewed", "path": "token-mediator/src/main/resources/application.yml", "heading": "AP2 session and OAuth client configuration", "line_start": 3, "line_end": 34, "url": "repo://keycloak-pattern/develop-keycloak-pattern2/token-mediator/src/main/resources/application.yml", "accessed": "", "claim_ids": [ "AP2-C3" ], "decision_ids": [], "priority": 85.0 }, { "id": "AP3_TRADEOFF", "title": "AP3와 AP1의 위협 모델·운영비 교환", "source_type": "branch-note", "status": "reviewed", "path": "docs/bff-vs-spa-direct.md", "heading": "BFF vs SPA direct", "line_start": 3, "line_end": 22, "url": "repo://keycloak-pattern/develop-keycloak-pattern3/docs/bff-vs-spa-direct.md", "accessed": "", "claim_ids": [ "AP3-C2" ], "decision_ids": [ "AP3-D2" ], "priority": 100.0 }, { "id": "AP3_PREFERENCE_SCOPE", "title": "AP3 preferences 예시의 process-global state 간극", "source_type": "canonical-project", "status": "reviewed-gap", "path": "bff/src/main/java/com/example/keycloakpattern/bff/BffController.java", "heading": "preferenceTheme and updatePreferences", "line_start": 28, "line_end": 95, "url": "repo://keycloak-pattern/develop-keycloak-pattern3/bff/src/main/java/com/example/keycloakpattern/bff/BffController.java", "accessed": "", "claim_ids": [ "AP3-C8" ], "decision_ids": [], "priority": 90.0 }, { "id": "AP4_BOUNDARY", "title": "AP4 oauth2-proxy와 nginx edge 책임 경계", "source_type": "branch-note", "status": "reviewed", "path": "docs/ap4-edge-forward-auth.md", "heading": "AP4 oauth2-proxy Edge Forward Auth", "line_start": 3, "line_end": 70, "url": "repo://keycloak-pattern/develop-keycloak-pattern4/docs/ap4-edge-forward-auth.md", "accessed": "", "claim_ids": [ "AP4-C1" ], "decision_ids": [ "AP4-D1" ], "priority": 100.0 }, { "id": "AP4_ALTERNATIVE", "title": "AP4 Traefik ForwardAuth 대안과 추가 비용", "source_type": "branch-note", "status": "config-tested", "path": "docs/traefik-forwardauth-alternative.md", "heading": "Traefik ForwardAuth alternative", "line_start": 3, "line_end": 22, "url": "repo://keycloak-pattern/develop-keycloak-pattern4/docs/traefik-forwardauth-alternative.md", "accessed": "", "claim_ids": [ "AP4-C4" ], "decision_ids": [ "AP4-D2" ], "priority": 85.0 } ], "evidence_gap": false }, { "section_id": "08-conclusion", "intent": "conclusion", "title": "결국 지키려던 것은 무엇이었나", "reader_question": "세부 기술을 걷어냈을 때 남는 판단은 무엇인가?", "decision_requirements": [], "evidence": [ { "id": "L4121b8d86b", "title": "four pattern tradeoff matrix — Four Keycloak integration patterns", "source_type": "local-document", "status": "", "path": "docs/four-pattern-tradeoff-matrix.md", "heading": "Four Keycloak integration patterns", "line_start": 1, "line_end": 14, "url": "repo:///docs/four-pattern-tradeoff-matrix.md", "accessed": "", "claim_ids": [], "decision_ids": [], "priority": 45.74042 }, { "id": "AP1_BOUNDARY", "title": "AP1 SPA direct의 OAuth·token 책임 경계", "source_type": "branch-note", "status": "reviewed", "path": "docs/internal-spa-direct-no-google.md", "heading": "AP1 internal SPA direct: local identity profile", "line_start": 3, "line_end": 10, "url": "repo://keycloak-pattern/develop-keycloak-pattern1/docs/internal-spa-direct-no-google.md", "accessed": "", "claim_ids": [ "AP1-C1" ], "decision_ids": [ "AP1-D1" ], "priority": 100.0 }, { "id": "AP2_BOUNDARY", "title": "AP2 confidential token mediator의 책임 경계", "source_type": "branch-note", "status": "reviewed", "path": "docs/ap2-token-boundary.md", "heading": "책임 경계", "line_start": 3, "line_end": 18, "url": "repo://keycloak-pattern/develop-keycloak-pattern2/docs/ap2-token-boundary.md", "accessed": "", "claim_ids": [ "AP2-C1" ], "decision_ids": [ "AP2-D1" ], "priority": 100.0 }, { "id": "AP3_BOUNDARY", "title": "AP3 BFF의 tokenless browser 경계", "source_type": "branch-note", "status": "reviewed", "path": "docs/ap3-bff-boundary.md", "heading": "요청과 token 경계", "line_start": 3, "line_end": 18, "url": "repo://keycloak-pattern/develop-keycloak-pattern3/docs/ap3-bff-boundary.md", "accessed": "", "claim_ids": [ "AP3-C1" ], "decision_ids": [ "AP3-D1" ], "priority": 100.0 }, { "id": "AP4_BOUNDARY", "title": "AP4 oauth2-proxy와 nginx edge 책임 경계", "source_type": "branch-note", "status": "reviewed", "path": "docs/ap4-edge-forward-auth.md", "heading": "AP4 oauth2-proxy Edge Forward Auth", "line_start": 3, "line_end": 70, "url": "repo://keycloak-pattern/develop-keycloak-pattern4/docs/ap4-edge-forward-auth.md", "accessed": "", "claim_ids": [ "AP4-C1" ], "decision_ids": [ "AP4-D1" ], "priority": 100.0 } ], "evidence_gap": false } ], "sources": [ { "id": "AP1_BOUNDARY", "title": "AP1 SPA direct의 OAuth·token 책임 경계", "source_type": "branch-note", "status": "reviewed", "path": "docs/internal-spa-direct-no-google.md", "heading": "AP1 internal SPA direct: local identity profile", "line_start": 3, "line_end": 10, "url": "repo://keycloak-pattern/develop-keycloak-pattern1/docs/internal-spa-direct-no-google.md", "accessed": "", "claim_ids": [ "AP1-C1" ], "decision_ids": [ "AP1-D1" ], "priority": 100.0 }, { "id": "AP1_STORAGE", "title": "AP1 token 저장 선택과 수용 비용", "source_type": "branch-note", "status": "reviewed", "path": "docs/ap1-token-storage.md", "heading": "AP1 token storage trade-off", "line_start": 3, "line_end": 29, "url": "repo://keycloak-pattern/develop-keycloak-pattern1/docs/ap1-token-storage.md", "accessed": "", "claim_ids": [ "AP1-C2" ], "decision_ids": [ "AP1-D2" ], "priority": 100.0 }, { "id": "AP1_LOGIN_RUNTIME", "title": "AP1 SPA authorization, callback와 browser token data flow", "source_type": "canonical-project", "status": "reviewed", "path": "frontend/src/app.js", "heading": "UserManager configuration, callback, and renderSession", "line_start": 9, "line_end": 81, "url": "repo://keycloak-pattern/develop-keycloak-pattern1/frontend/src/app.js", "accessed": "", "claim_ids": [ "AP1-C4" ], "decision_ids": [], "priority": 100.0 }, { "id": "AP1_PKCE_DEMO_GAP", "title": "AP1 manual PKCE helper와 실제 signin path의 구분", "source_type": "canonical-project", "status": "reviewed-gap", "path": "frontend/src/pkce.js", "heading": "createPkcePair", "line_start": 1, "line_end": 25, "url": "repo://keycloak-pattern/develop-keycloak-pattern1/frontend/src/pkce.js", "accessed": "", "claim_ids": [ "AP1-C5" ], "decision_ids": [], "priority": 85.0 }, { "id": "AP1_API_RUNTIME", "title": "AP1 browser Bearer input에서 /api/me JSON까지", "source_type": "canonical-project", "status": "reviewed", "path": "frontend/src/app.js", "heading": "callProtectedApi", "line_start": 83, "line_end": 109, "url": "repo://keycloak-pattern/develop-keycloak-pattern1/frontend/src/app.js", "accessed": "", "claim_ids": [ "AP1-C6" ], "decision_ids": [], "priority": 100.0 }, { "id": "AP1_ROLE_FAILURE_RUNTIME", "title": "AP1 JWT failure와 realm role authorization 경계", "source_type": "canonical-project", "status": "reviewed-gap", "path": "backend/src/main/java/com/example/keycloakpattern/KeycloakRealmRoleConverter.java", "heading": "convert", "line_start": 12, "line_end": 27, "url": "repo://keycloak-pattern/develop-keycloak-pattern1/backend/src/main/java/com/example/keycloakpattern/KeycloakRealmRoleConverter.java", "accessed": "", "claim_ids": [ "AP1-C7" ], "decision_ids": [], "priority": 90.0 }, { "id": "AP1_GUARDRAILS", "title": "AP1 public client와 Resource Server 가드레일", "source_type": "canonical-project", "status": "reviewed", "path": "keycloak/import/keycloak-patterns-realm.json", "heading": "spa-public client and realm token settings", "line_start": 11, "line_end": 73, "url": "repo://keycloak-pattern/develop-keycloak-pattern1/keycloak/import/keycloak-patterns-realm.json", "accessed": "", "claim_ids": [ "AP1-C3" ], "decision_ids": [], "priority": 85.0 }, { "id": "AP1_VERIFY", "title": "AP1 브라우저 흐름과 token 수명주기 검증 계약", "source_type": "canonical-project", "status": "test-defined", "path": "e2e/pattern1.mjs", "heading": "AP1 Playwright acceptance contract", "line_start": 82, "line_end": 202, "url": "repo://keycloak-pattern/develop-keycloak-pattern1/e2e/pattern1.mjs", "accessed": "", "claim_ids": [ "AP1-T1" ], "decision_ids": [], "priority": 80.0 }, { "id": "AP2_BOUNDARY", "title": "AP2 confidential token mediator의 책임 경계", "source_type": "branch-note", "status": "reviewed", "path": "docs/ap2-token-boundary.md", "heading": "책임 경계", "line_start": 3, "line_end": 18, "url": "repo://keycloak-pattern/develop-keycloak-pattern2/docs/ap2-token-boundary.md", "accessed": "", "claim_ids": [ "AP2-C1" ], "decision_ids": [ "AP2-D1" ], "priority": 100.0 }, { "id": "AP2_IMPLEMENTATION", "title": "AP2 access-only handoff의 실제 구현", "source_type": "canonical-project", "status": "reviewed-discrepancy", "path": "token-mediator/src/main/java/com/example/keycloakpattern/mediator/AccessTokenController.java", "heading": "accessToken", "line_start": 28, "line_end": 55, "url": "repo://keycloak-pattern/develop-keycloak-pattern2/token-mediator/src/main/java/com/example/keycloakpattern/mediator/AccessTokenController.java", "accessed": "", "claim_ids": [ "AP2-C2" ], "decision_ids": [], "priority": 100.0 }, { "id": "AP2_LOGIN_FLOW", "title": "AP2 browser entry와 Spring oauth2Login code 교환", "source_type": "canonical-project", "status": "reviewed", "path": "token-mediator/src/main/resources/static/app.js", "heading": "loginButton click and OAuth client registration", "line_start": 7, "line_end": 9, "url": "repo://keycloak-pattern/develop-keycloak-pattern2/token-mediator/src/main/resources/static/app.js", "accessed": "", "claim_ids": [ "AP2-C4" ], "decision_ids": [], "priority": 90.0 }, { "id": "AP2_BOUNDARY_RUNTIME", "title": "AP2 token boundary endpoint input과 output", "source_type": "canonical-project", "status": "reviewed", "path": "token-mediator/src/main/java/com/example/keycloakpattern/mediator/TokenBoundaryController.java", "heading": "tokenBoundary", "line_start": 25, "line_end": 42, "url": "repo://keycloak-pattern/develop-keycloak-pattern2/token-mediator/src/main/java/com/example/keycloakpattern/mediator/TokenBoundaryController.java", "accessed": "", "claim_ids": [ "AP2-C5" ], "decision_ids": [], "priority": 95.0 }, { "id": "AP2_ACCESS_RUNTIME", "title": "AP2 access handoff와 browser direct API의 data transformation", "source_type": "canonical-project", "status": "reviewed", "path": "token-mediator/src/main/java/com/example/keycloakpattern/mediator/AccessTokenController.java", "heading": "accessToken", "line_start": 28, "line_end": 54, "url": "repo://keycloak-pattern/develop-keycloak-pattern2/token-mediator/src/main/java/com/example/keycloakpattern/mediator/AccessTokenController.java", "accessed": "", "claim_ids": [ "AP2-C6" ], "decision_ids": [], "priority": 100.0 }, { "id": "AP2_RESOURCE_RUNTIME", "title": "AP2 Resource Server의 JWT input과 /api/me output", "source_type": "canonical-project", "status": "reviewed", "path": "backend/src/main/java/com/example/keycloakpattern/ApiController.java", "heading": "currentUser", "line_start": 21, "line_end": 28, "url": "repo://keycloak-pattern/develop-keycloak-pattern2/backend/src/main/java/com/example/keycloakpattern/ApiController.java", "accessed": "", "claim_ids": [ "AP2-C7" ], "decision_ids": [], "priority": 95.0 }, { "id": "AP2_GUARDRAILS", "title": "AP2 session, refresh custody, CORS와 audience 가드레일", "source_type": "canonical-project", "status": "reviewed", "path": "token-mediator/src/main/resources/application.yml", "heading": "AP2 session and OAuth client configuration", "line_start": 3, "line_end": 34, "url": "repo://keycloak-pattern/develop-keycloak-pattern2/token-mediator/src/main/resources/application.yml", "accessed": "", "claim_ids": [ "AP2-C3" ], "decision_ids": [], "priority": 85.0 }, { "id": "AP2_VERIFY", "title": "AP2 access-only 전달과 브라우저 직접 API 호출 검증 계약", "source_type": "canonical-project", "status": "test-defined", "path": "e2e/pattern2.mjs", "heading": "AP2 Playwright acceptance contract", "line_start": 39, "line_end": 112, "url": "repo://keycloak-pattern/develop-keycloak-pattern2/e2e/pattern2.mjs", "accessed": "", "claim_ids": [ "AP2-T1" ], "decision_ids": [], "priority": 80.0 }, { "id": "AP3_BOUNDARY", "title": "AP3 BFF의 tokenless browser 경계", "source_type": "branch-note", "status": "reviewed", "path": "docs/ap3-bff-boundary.md", "heading": "요청과 token 경계", "line_start": 3, "line_end": 18, "url": "repo://keycloak-pattern/develop-keycloak-pattern3/docs/ap3-bff-boundary.md", "accessed": "", "claim_ids": [ "AP3-C1" ], "decision_ids": [ "AP3-D1" ], "priority": 100.0 }, { "id": "AP3_TRADEOFF", "title": "AP3와 AP1의 위협 모델·운영비 교환", "source_type": "branch-note", "status": "reviewed", "path": "docs/bff-vs-spa-direct.md", "heading": "BFF vs SPA direct", "line_start": 3, "line_end": 22, "url": "repo://keycloak-pattern/develop-keycloak-pattern3/docs/bff-vs-spa-direct.md", "accessed": "", "claim_ids": [ "AP3-C2" ], "decision_ids": [ "AP3-D2" ], "priority": 100.0 }, { "id": "AP3_GUARDRAILS", "title": "AP3 CSRF token과 SameSite 가드레일", "source_type": "canonical-project", "status": "reviewed", "path": "bff/src/main/java/com/example/keycloakpattern/bff/SecurityConfig.java", "heading": "bffSecurity", "line_start": 25, "line_end": 58, "url": "repo://keycloak-pattern/develop-keycloak-pattern3/bff/src/main/java/com/example/keycloakpattern/bff/SecurityConfig.java", "accessed": "", "claim_ids": [ "AP3-C3" ], "decision_ids": [], "priority": 85.0 }, { "id": "AP3_LOGIN_FLOW", "title": "AP3 oauth2Login과 server-side PKCE data flow", "source_type": "canonical-project", "status": "reviewed", "path": "bff/src/main/java/com/example/keycloakpattern/bff/SecurityConfig.java", "heading": "authorizationRequestResolver and bffSecurity", "line_start": 20, "line_end": 58, "url": "repo://keycloak-pattern/develop-keycloak-pattern3/bff/src/main/java/com/example/keycloakpattern/bff/SecurityConfig.java", "accessed": "", "claim_ids": [ "AP3-C4" ], "decision_ids": [], "priority": 95.0 }, { "id": "AP3_BOUNDARY_RUNTIME", "title": "AP3 token boundary endpoint의 input과 관측 output", "source_type": "canonical-project", "status": "reviewed", "path": "bff/src/main/java/com/example/keycloakpattern/bff/BffController.java", "heading": "tokenBoundary", "line_start": 44, "line_end": 64, "url": "repo://keycloak-pattern/develop-keycloak-pattern3/bff/src/main/java/com/example/keycloakpattern/bff/BffController.java", "accessed": "", "claim_ids": [ "AP3-C5" ], "decision_ids": [], "priority": 95.0 }, { "id": "AP3_API_RUNTIME", "title": "AP3 session input에서 downstream Bearer와 reader JSON까지", "source_type": "canonical-project", "status": "reviewed", "path": "bff/src/main/java/com/example/keycloakpattern/bff/BffController.java", "heading": "currentUser and authorizedClient", "line_start": 67, "line_end": 111, "url": "repo://keycloak-pattern/develop-keycloak-pattern3/bff/src/main/java/com/example/keycloakpattern/bff/BffController.java", "accessed": "", "claim_ids": [ "AP3-C6" ], "decision_ids": [], "priority": 100.0 }, { "id": "AP3_CSRF_RUNTIME", "title": "AP3 CSRF cookie-to-header transformation과 preferences output", "source_type": "canonical-project", "status": "reviewed", "path": "bff/src/main/java/com/example/keycloakpattern/bff/CsrfController.java", "heading": "csrf", "line_start": 14, "line_end": 23, "url": "repo://keycloak-pattern/develop-keycloak-pattern3/bff/src/main/java/com/example/keycloakpattern/bff/CsrfController.java", "accessed": "", "claim_ids": [ "AP3-C7" ], "decision_ids": [], "priority": 100.0 }, { "id": "AP3_PREFERENCE_SCOPE", "title": "AP3 preferences 예시의 process-global state 간극", "source_type": "canonical-project", "status": "reviewed-gap", "path": "bff/src/main/java/com/example/keycloakpattern/bff/BffController.java", "heading": "preferenceTheme and updatePreferences", "line_start": 28, "line_end": 95, "url": "repo://keycloak-pattern/develop-keycloak-pattern3/bff/src/main/java/com/example/keycloakpattern/bff/BffController.java", "accessed": "", "claim_ids": [ "AP3-C8" ], "decision_ids": [], "priority": 90.0 }, { "id": "AP3_VERIFY", "title": "AP3 browser token 비노출과 CSRF 방어 검증 계약", "source_type": "canonical-project", "status": "test-defined", "path": "e2e/pattern3.mjs", "heading": "AP3 Playwright acceptance contract", "line_start": 43, "line_end": 191, "url": "repo://keycloak-pattern/develop-keycloak-pattern3/e2e/pattern3.mjs", "accessed": "", "claim_ids": [ "AP3-T1" ], "decision_ids": [], "priority": 80.0 }, { "id": "AP4_BOUNDARY", "title": "AP4 oauth2-proxy와 nginx edge 책임 경계", "source_type": "branch-note", "status": "reviewed", "path": "docs/ap4-edge-forward-auth.md", "heading": "AP4 oauth2-proxy Edge Forward Auth", "line_start": 3, "line_end": 70, "url": "repo://keycloak-pattern/develop-keycloak-pattern4/docs/ap4-edge-forward-auth.md", "accessed": "", "claim_ids": [ "AP4-C1" ], "decision_ids": [ "AP4-D1" ], "priority": 100.0 }, { "id": "AP4_NGINX", "title": "AP4 auth_request와 identity header 덮어쓰기", "source_type": "canonical-project", "status": "reviewed", "path": "frontend/default.conf.template", "heading": "nginx AP4 server configuration", "line_start": 13, "line_end": 74, "url": "repo://keycloak-pattern/develop-keycloak-pattern4/frontend/default.conf.template", "accessed": "", "claim_ids": [ "AP4-C2" ], "decision_ids": [], "priority": 90.0 }, { "id": "AP4_LOGIN_RUNTIME", "title": "AP4 unauthenticated navigation에서 oauth2-proxy session까지", "source_type": "canonical-project", "status": "reviewed", "path": "docker-compose.yml", "heading": "oauth2-proxy service", "line_start": 92, "line_end": 143, "url": "repo://keycloak-pattern/develop-keycloak-pattern4/docker-compose.yml", "accessed": "", "claim_ids": [ "AP4-C5" ], "decision_ids": [], "priority": 100.0 }, { "id": "AP4_REQUEST_RUNTIME", "title": "AP4 external request에서 auth subrequest와 upstream input까지", "source_type": "canonical-project", "status": "reviewed", "path": "frontend/default.conf.template", "heading": "auth_request and upstream mapping", "line_start": 13, "line_end": 74, "url": "repo://keycloak-pattern/develop-keycloak-pattern4/frontend/default.conf.template", "accessed": "", "claim_ids": [ "AP4-C6" ], "decision_ids": [], "priority": 100.0 }, { "id": "AP4_RESPONSE_RUNTIME", "title": "AP4 trusted header input에서 edge identity JSON까지", "source_type": "canonical-project", "status": "reviewed", "path": "backend/src/main/java/com/example/keycloakpattern/EdgeIdentityController.java", "heading": "currentUser and hasValidInternalToken", "line_start": 27, "line_end": 53, "url": "repo://keycloak-pattern/develop-keycloak-pattern4/backend/src/main/java/com/example/keycloakpattern/EdgeIdentityController.java", "accessed": "", "claim_ids": [ "AP4-C7" ], "decision_ids": [], "priority": 100.0 }, { "id": "AP4_BACKEND", "title": "AP4 upstream의 내부 token 검증", "source_type": "canonical-project", "status": "reviewed", "path": "backend/src/main/java/com/example/keycloakpattern/EdgeIdentityController.java", "heading": "currentUser and hasValidInternalToken", "line_start": 18, "line_end": 53, "url": "repo://keycloak-pattern/develop-keycloak-pattern4/backend/src/main/java/com/example/keycloakpattern/EdgeIdentityController.java", "accessed": "", "claim_ids": [ "AP4-C3" ], "decision_ids": [], "priority": 85.0 }, { "id": "AP4_VERIFY", "title": "AP4 edge login과 header spoofing 방어 검증 계약", "source_type": "canonical-project", "status": "test-defined", "path": "e2e/pattern4.mjs", "heading": "AP4 Playwright acceptance contract", "line_start": 44, "line_end": 131, "url": "repo://keycloak-pattern/develop-keycloak-pattern4/e2e/pattern4.mjs", "accessed": "", "claim_ids": [ "AP4-T1" ], "decision_ids": [], "priority": 80.0 }, { "id": "AP4_ALTERNATIVE", "title": "AP4 Traefik ForwardAuth 대안과 추가 비용", "source_type": "branch-note", "status": "config-tested", "path": "docs/traefik-forwardauth-alternative.md", "heading": "Traefik ForwardAuth alternative", "line_start": 3, "line_end": 22, "url": "repo://keycloak-pattern/develop-keycloak-pattern4/docs/traefik-forwardauth-alternative.md", "accessed": "", "claim_ids": [ "AP4-C4" ], "decision_ids": [ "AP4-D2" ], "priority": 85.0 }, { "id": "BRANCH_REACHABILITY", "title": "네 pattern branch와 39개 feature ref의 도달성", "source_type": "canonical-project", "status": "partially-verified", "path": "docs/keycloak-branch-manifest.tsv", "heading": "branch target delivery registry", "line_start": 1, "line_end": 40, "url": "repo://keycloak-pattern/develop/docs/keycloak-branch-manifest.tsv", "accessed": "", "claim_ids": [ "COMMON-T1" ], "decision_ids": [], "priority": 70.0 }, { "id": "L4121b8d86b", "title": "four pattern tradeoff matrix — Four Keycloak integration patterns", "source_type": "local-document", "status": "", "path": "docs/four-pattern-tradeoff-matrix.md", "heading": "Four Keycloak integration patterns", "line_start": 1, "line_end": 14, "url": "repo:///docs/four-pattern-tradeoff-matrix.md", "accessed": "", "claim_ids": [], "decision_ids": [], "priority": 45.74042 }, { "id": "La5d0a70f24", "title": "four pattern tradeoff matrix — 이 repository의 실행 증거", "source_type": "local-document", "status": "", "path": "docs/four-pattern-tradeoff-matrix.md", "heading": "이 repository의 실행 증거", "line_start": 28, "line_end": 37, "url": "repo:///docs/four-pattern-tradeoff-matrix.md", "accessed": "", "claim_ids": [], "decision_ids": [], "priority": 21.712857 }, { "id": "L2c120c8093", "title": "four pattern tradeoff matrix — 선택 기준", "source_type": "local-document", "status": "", "path": "docs/four-pattern-tradeoff-matrix.md", "heading": "선택 기준", "line_start": 15, "line_end": 27, "url": "repo:///docs/four-pattern-tradeoff-matrix.md", "accessed": "", "claim_ids": [], "decision_ids": [], "priority": 17.280962 }, { "id": "L4ec23ba045", "title": "keycloak branch index — Keycloak branch implementation index", "source_type": "local-document", "status": "", "path": "docs/keycloak-branch-index.md", "heading": "Keycloak branch implementation index", "line_start": 1, "line_end": 29, "url": "repo:///docs/keycloak-branch-index.md", "accessed": "", "claim_ids": [], "decision_ids": [], "priority": 14.830096 }, { "id": "Lb39734ea9b", "title": "google idp brokering — Google IdP brokering", "source_type": "local-document", "status": "", "path": "docs/google-idp-brokering.md", "heading": "Google IdP brokering", "line_start": 1, "line_end": 5, "url": "repo:///docs/google-idp-brokering.md", "accessed": "", "claim_ids": [], "decision_ids": [], "priority": 5.851474 }, { "id": "La28755902d", "title": "google claim to role — Google claim-to-role mapping", "source_type": "local-document", "status": "", "path": "docs/google-claim-to-role.md", "heading": "Google claim-to-role mapping", "line_start": 1, "line_end": 18, "url": "repo:///docs/google-claim-to-role.md", "accessed": "", "claim_ids": [], "decision_ids": [], "priority": 4.750257 }, { "id": "Le8474e5ddd", "title": "https termination — HTTPS termination: nginx or Caddy", "source_type": "local-document", "status": "", "path": "docs/https-termination.md", "heading": "HTTPS termination: nginx or Caddy", "line_start": 1, "line_end": 20, "url": "repo:///docs/https-termination.md", "accessed": "", "claim_ids": [], "decision_ids": [], "priority": 2.684955 }, { "id": "L0eb117abf5", "title": "google redirect uri policy — Google redirect URI policy", "source_type": "local-document", "status": "", "path": "docs/google-redirect-uri-policy.md", "heading": "Google redirect URI policy", "line_start": 1, "line_end": 24, "url": "repo:///docs/google-redirect-uri-policy.md", "accessed": "", "claim_ids": [], "decision_ids": [], "priority": 2.514945 }, { "id": "L5d2c3b8016", "title": "reverse proxy headers — Reverse proxy headers", "source_type": "local-document", "status": "", "path": "docs/reverse-proxy-headers.md", "heading": "Reverse proxy headers", "line_start": 1, "line_end": 15, "url": "repo:///docs/reverse-proxy-headers.md", "accessed": "", "claim_ids": [], "decision_ids": [], "priority": 1.816984 }, { "id": "L03b6abccb3", "title": "google claim mapping — Google claim and identity mapping", "source_type": "local-document", "status": "", "path": "docs/google-claim-mapping.md", "heading": "Google claim and identity mapping", "line_start": 1, "line_end": 23, "url": "repo:///docs/google-claim-mapping.md", "accessed": "", "claim_ids": [], "decision_ids": [], "priority": 1.503831 }, { "id": "L0217277f31", "title": "account linking sub vs email — Federated account key: `sub`, not email", "source_type": "local-document", "status": "", "path": "docs/account-linking-sub-vs-email.md", "heading": "Federated account key: `sub`, not email", "line_start": 1, "line_end": 18, "url": "repo:///docs/account-linking-sub-vs-email.md", "accessed": "", "claim_ids": [], "decision_ids": [], "priority": 1.49767 }, { "id": "L4a3b756b3d", "title": "google idp brokering — Two verification profiles", "source_type": "local-document", "status": "", "path": "docs/google-idp-brokering.md", "heading": "Two verification profiles", "line_start": 6, "line_end": 28, "url": "repo:///docs/google-idp-brokering.md", "accessed": "", "claim_ids": [], "decision_ids": [], "priority": 1.020519 }, { "id": "Le9a41ffd86", "title": "public domain tunneling — Public HTTPS domain for broker callbacks", "source_type": "local-document", "status": "", "path": "docs/public-domain-tunneling.md", "heading": "Public HTTPS domain for broker callbacks", "line_start": 1, "line_end": 21, "url": "repo:///docs/public-domain-tunneling.md", "accessed": "", "claim_ids": [], "decision_ids": [], "priority": 0.729912 }, { "id": "L55212df816", "title": "first broker login security — First Broker Login security", "source_type": "local-document", "status": "", "path": "docs/first-broker-login-security.md", "heading": "First Broker Login security", "line_start": 1, "line_end": 27, "url": "repo:///docs/first-broker-login-security.md", "accessed": "", "claim_ids": [], "decision_ids": [], "priority": 0.088108 } ] }