Files
2026-07-29 18:03:21 +09:00

170 lines
4.9 KiB
JSON

{
"version": "1.1",
"id": "ap1-direct-architecture",
"title": "AP1은 OAuth client와 token custody를 브라우저에 둔다",
"question": "AP1에서 code 교환, token 보관, Resource Server JWT 입력은 어느 구성 요소에 놓이는가?",
"type": "architecture",
"direction": "LR",
"audience": [
"SPA OAuth와 Resource Server 경계를 설계하는 개발자"
],
"summary": "Public SPA가 PKCE code 교환을 수행하고 token set을 JavaScript memory에 보관한 뒤 access JWT를 Resource Server 입력으로 사용한다.",
"alt": "SPA, Keycloak, 브라우저 JavaScript memory, Resource Server가 왼쪽에서 오른쪽으로 연결된 AP1 직접 인증 아키텍처.",
"long_description": "왼쪽의 public SPA가 Keycloak과 Authorization Code 및 PKCE S256 계약을 수행한다. Keycloak token 응답의 access, refresh, ID token은 브라우저 JavaScript memory에 놓이며, 그중 access JWT가 오른쪽 Resource Server의 검증 입력이 된다. Resource Server는 issuer, 시간 제약과 keycloak-pattern-api audience를 검증한다.",
"source_context": {
"document": "document.md",
"document_sha256": "df4d1a604c74e756672b5b40510abfedb8c67b39af280a5f51985ea9972f5371",
"anchor": {
"kind": "marker",
"value": "ap1-direct-architecture",
"line": 152
}
},
"composition": {
"profile": "component-flow",
"diagram_only": true,
"reference_ids": [
"payment-event-flow"
],
"rationale": "AP1의 핵심은 SPA에서 시작한 code와 token이 브라우저 memory를 거쳐 Resource Server JWT 입력으로 이동하는 방향성 있는 구성 요소 경로다.",
"focus_node": "browser-spa"
},
"groups": [],
"nodes": [
{
"id": "browser-spa",
"label": "Public SPA",
"kind": "service",
"role": "source",
"emphasis": "primary",
"description": "Authorization Code와 PKCE를 직접 다루고 access JWT로 Resource Server를 호출하는 public OAuth client.",
"details": [
"spa-public",
"PKCE S256"
],
"evidence": [
{
"start_line": 144,
"end_line": 148
}
],
"assumption": false
},
{
"id": "browser-token-memory",
"label": "Browser JS memory",
"kind": "database",
"role": "store",
"shape": "database",
"description": "Access, refresh, ID token을 실행 중 보관하며 reload 뒤에는 복구하지 않는 browser-side custody.",
"details": [
"access · refresh · ID token",
"persistent Web Storage 없음"
],
"evidence": [
{
"start_line": 148,
"end_line": 150
}
],
"assumption": false
},
{
"id": "keycloak",
"label": "Keycloak",
"kind": "service",
"role": "service",
"description": "SPA의 code 교환 상대이며 API가 검증하는 JWT 서명의 출처.",
"evidence": [
{
"start_line": 144,
"end_line": 146
}
],
"assumption": false
},
{
"id": "api-resource-server",
"label": "Resource Server",
"kind": "service",
"role": "sink",
"description": "Access JWT의 issuer, 시간 제약과 audience를 검증하는 API.",
"details": [
"issuer · time",
"keycloak-pattern-api audience"
],
"evidence": [
{
"start_line": 144,
"end_line": 150
}
],
"assumption": false
}
],
"edges": [
{
"id": "spa-to-keycloak",
"from": "browser-spa",
"to": "keycloak",
"label": "Authorization Code + PKCE S256",
"kind": "request",
"evidence": [
{
"start_line": 144,
"end_line": 146
}
],
"assumption": false
},
{
"id": "keycloak-to-spa",
"from": "keycloak",
"to": "browser-spa",
"label": "access · refresh · ID token",
"kind": "response",
"style": "dashed",
"evidence": [
{
"start_line": 144,
"end_line": 148
}
],
"assumption": false
},
{
"id": "spa-to-memory",
"from": "browser-spa",
"to": "browser-token-memory",
"label": "token set 보관",
"kind": "data",
"evidence": [
{
"start_line": 148,
"end_line": 150
}
],
"assumption": false
},
{
"id": "spa-to-resource-server",
"from": "browser-spa",
"to": "api-resource-server",
"label": "memory-held access JWT · Bearer",
"kind": "request",
"emphasis": "primary",
"evidence": [
{
"start_line": 144,
"end_line": 150
}
],
"assumption": false
}
],
"legend": [],
"metadata": {
"rationale": "로그인 message 순서가 아니라 OAuth client, token custody, JWT 검증 위치를 한 수준의 component path로 표현했다."
}
}