인증 API 맥락 (Refresh 쿠키)

스펙 원본은 frontmatter spec:. 전체 계약은 auth-refresh-cookie-api.md. 이 문서는 설계 이유와 FE·BE가 함께 지켜야 하는 흐름만.

한눈에

엔드포인트역할 한 줄
POST /v1/auth/google/completeOAuth ticket → access token(body) + refresh(HttpOnly 쿠키)
POST /v1/auth/token/refresh쿠키 검증 후 access·refresh 동시 재발급
POST /v1/auth/logoutrefresh 쿠키 만료(Max-Age=0)

왜 이렇게 생겼나

  • Refresh는 stateless JWT + HttpOnly 쿠키. DB·Redis 저장 없음(운영 단순), localStorage 대비 XSS로 토큰 자체를 읽을 수 없음. 대가는 아래 “알려진 제약”.
  • 쿠키 Path=/v1/auth — refresh 토큰이 인증 API 호출에만 실려 다닌다. 일반 API 요청엔 아예 전송 안 됨(노출 면적 최소화).
  • access token은 body로 반환 — FE가 메모리에 두고 Authorization 헤더로 사용.

FE·BE가 함께 지키는 흐름

보호 API 401 → refresh 1회(single-flight) → 성공: 원요청 1회만 재시도
                                          → 실패: POST /logout → /login 이동
  • single-flight: 여러 API가 동시에 401이어도 refresh 요청은 하나만.
  • 재발급 실패 시 서버가 쿠키를 자동 삭제하지 않는다 — FE가 반드시 /logout을 호출해 HttpOnly 쿠키를 만료시켜야 함. 안 하면 만료·손상 쿠키로 refresh 무한 실패 루프.
  • refresh 자체의 실패는 다시 refresh하지 않는다. 원요청 재시도는 최대 1회.
  • 모든 인증 호출은 credentials: 'include' 필수 — 빠지면 쿠키가 안 실려 로컬에서만 재현 안 되는 401이 된다.

알려진 제약 (stateless 선택의 대가)

  • 로그아웃은 브라우저 쿠키만 삭제 — 탈취된 refresh JWT는 만료 전까지 유효.
  • 재사용 감지, 기기별 세션, 전체 기기 로그아웃 없음.
  • 탈퇴 회원은 토큰이 남아 있어도 재발급 거부(US0002).
  • 이 제약이 문제 되면(보안 사고, 세션 관리 요구) DB 저장 방식 재검토 — 그때 ADR로.

엣지케이스 · gotcha

  • SameSite=Lax 기본. FE와 API가 다른 사이트로 갈라질 때만 None+Secure — 그리고 그 경우 CORS origin은 정확한 값만(와일드카드 불가). 로컬 HTTP는 REFRESH_COOKIE_SECURE=false.
  • 오류 코드 분기: AU0001(쿠키 없음, 401) vs AU0005(만료·서명 불량, 400) — HTTP status가 다르다는 점 주의.

관련 문서