본문으로 건너뛰기

Kakao OAuth API 연동 계약 (웹 프런트엔드)

본 문서는 SleepZ 웹 프런트엔드가 Kakao OAuth 로그인/회원가입을 구현할 때 참조해야 할 백엔드 API 계약을 정리한 것입니다. 자세한 도메인 스펙은 docs/domains/kr/core-domains/auth/endpoints.md 및 관련 문서를 우선 참조합니다.

1. 연동 개요

  • 대상 백엔드: apps/dha-sleep-api
  • 관련 도메인 문서:
  • 프런트엔드 역할:
    1. 인가 URL 요청 → state 쿠키 수신
    2. Kakao 권한 페이지로 리다이렉트
    3. 콜백에서 code/state 확인 후 백엔드에 교환 요청
    4. 발급된 JWT를 HttpOnly 쿠키로 수신, 이후 보호된 페이지 접근
    5. (신규 사용자) AppToken을 이용해 온보딩 폼 제출, 운영 승인 후 재로그인

⚠️ 사전 조건: 모든 Kakao 교환 및 온보딩 요청은 POST /auth/app/challengePOST /auth/app/complete-challenge 앱 인증 흐름을 통과해 appToken을 발급받은 상태여야 합니다.

2. 엔드포인트 요약

단계HTTP엔드포인트인증설명
인가 URL 초기화GET/auth/oauth/kakaoPublicstate 생성, Kakao 인가 URL 제공
Kakao 콜백 처리GET/auth/oauth/kakao/callbackPubliccode/state 검증, 토큰 교환, 최종 리다이렉트
토큰 교환 헬퍼 (웹)POST/auth/oauth/kakao/exchangePublic프런트 전용. 콜백에서 받은 code를 백엔드에 전달해 JWT 또는 프로비저닝 티켓 획득
온보딩 제출POST/auth/oauth/kakao/provisioning/completeApp TokenappToken 기반으로 온보딩 폼 데이터를 제출하고 프로비저닝 세션을 완료
(기존 사용자) 프로필 업데이트PUT/users/profileAccess JWT이미 계정이 있는 사용자의 기본 정보 갱신

GET /auth/oauth/kakao/callback은 서버-사이드 리다이렉트용 기본 엔드포인트이며, 웹 프런트엔드는 App Router에서 POST /auth/oauth/kakao/exchange를 활용해 code를 교환하는 패턴을 사용합니다. 두 흐름 모두 동일한 도메인 규칙을 따릅니다.

모바일 앱과의 차이

  • 모바일은 sleepz://와 같은 커스텀 스킴으로 콜백을 수신하고, 쿠키 대신 앱 보안 스토리지에 토큰을 저장합니다.
  • HttpOnly 쿠키 기반 세션(웹)과 달리 모바일은 Bearer 토큰을 직접 관리하므로 POST /auth/oauth/kakao/exchange를 그대로 사용할 이유가 없습니다.
  • 모바일 통합 시에는 표준 콜백(GET /auth/oauth/kakao/callback) 또는 모바일 전용 API를 통해 토큰을 발급받는 전략을 사용하며, 상세 가이드는 frontend/mobile/integration-guides/kakao-oauth-integration.md를 참고하세요.

3. 상세 요청/응답

3.1. GET /auth/oauth/kakao

  • Query: 없음
  • Headers
    • X-CSRF-Token: Optional (운영/QA 환경에서만 요구될 수 있음)
  • 성공 응답
{
"authorizationUrl": "https://kauth.kakao.com/oauth/authorize?client_id=...&redirect_uri=...",
"state": "f1556c2f-6ff4-4a47-a823-4f8a4fdba921",
"expireAt": "2025-01-20T10:32:00.000Z"
}
  • Set-Cookie
    • kakao_auth_state={UUID}; HttpOnly; Secure; SameSite=Lax; Max-Age=600
  • 프런트 처리
    • 응답 수신 후 authorizationUrl로 즉시 리다이렉트
    • state 값과 쿠키 일치 여부는 콜백 단계에서 자동 검증

3.2. POST /auth/oauth/kakao/exchange

  • Headers

    • Authorization: Bearer {appToken}
  • Body

{
"code": "kauth-authorization-code",
"redirectUri": "https://sleepz.dev/auth/callback"
}
  • 성공 응답
{
"accessToken": "jwt-access-token",
"refreshToken": "jwt-refresh-token",
"expiresIn": 3600,
"requiresProfileSetup": false,
"user": {
"id": "user-uuid",
"email": "operator@sleepz.dev",
"roles": ["ROLE_OPERATOR"]
}
}
  • 신규 사용자 응답
{
"accessToken": null,
"refreshToken": null,
"expiresIn": null,
"requiresProfileSetup": true,
"user": null,
"state": "f1556c2f-6ff4-4a47-a823-4f8a4fdba921",
"scope": ["profile", "account_email"]
}
  • Set-Cookie
    • sleepz_access_token=...; HttpOnly; Secure; SameSite=Strict; Path=/
    • sleepz_refresh_token=...; HttpOnly; Secure; SameSite=Strict; Path=/auth/refresh
  • 오류 응답 예시
    • 400 AUTH_KAKAO_STATE_MISMATCH
    • 400 AUTH_KAKAO_CODE_EXPIRED
    • 503 AUTH_KAKAO_PROVIDER_DOWN
  • 프런트 처리
    • 기존 사용자 → JWT 쿠키를 설정하고 /dashboard로 이동
    • 신규 사용자 → appToken 쿠키(sleepz_app_token)를 유지하고 /onboarding/profile로 이동
    • 401 또는 403 → 토스트 노출 후 /login으로 이동
    • 503 → 재시도 안내 메시지, Contact 지원 링크 포함

3.3. POST /auth/oauth/kakao/provisioning/complete

  • Headers

    • Authorization: Bearer {appToken}
  • Body

{
"name": "홍길동",
"organization": "SleepZ 운영팀",
"email": "operator@sleepz.dev"
}
  • 성공 응답 (202 Accepted)
{
"status": "accepted",
"message": "프로비저닝 요청이 접수되었습니다. 검토 후 이용 가능 여부를 안내드릴게요.",
"next": "/login?reason=session-required"
}
  • 오류 응답 예시

    • 400 KAKAO_PROVISIONING_SESSION_INVALID
  • 프런트 처리

    • 성공 시 /login?reason=session-required&provisioning=submitted로 이동 (appToken은 유지)
    • 오류 시 사용자에게 재시도 안내 후 /login으로 복귀

3.4. PUT /users/profile

  • 헤더: Authorization: Bearer {accessToken}
  • Body
{
"name": "홍길동",
"organization": "SleepZ QA Team",
"email": "qa@sleepz.dev"
}
  • 응답
{
"userId": "user-uuid",
"updatedAt": "2025-01-20T11:05:00.000Z"
}
  • 오류
    • 400 USER_PROFILE_INVALID_FIELD
    • 401 AUTH_TOKEN_EXPIRED
  • 프런트 처리
    • 401 → 토큰 갱신 로직 준비 전까지 /login으로 복귀

4. 상태 검증 및 보안 고려 사항

항목프런트 처리
state 검증백엔드에서 쿠키 값과 비교. 프런트는 추가 조치 불필요하지만, 쿠키 삭제 금지
state TTL10분 초과 시 400 AUTH_KAKAO_STATE_MISMATCH. 로그인 페이지에서 재시도 버튼 제공
CSRF 헤더Next.js App Router의 Route Handler에서 X-CSRF-Token을 전달하도록 유틸 함수 준비
TimeMachine백엔드 토큰 만료/로그 기록은 TimeMachine 시계 기반. 프런트 표시용 타임스탬프는 luxon 등으로 사용자 시간대에 맞춰 변환
쿠키 보안HttpOnly 쿠키이므로 프런트 JavaScript에서 접근 불가. 상태 확인은 백엔드 세션 검증 API를 통해 수행
앱 토큰 쿠키sleepz_app_token은 HttpOnly 쿠키로 저장되며, Kakao 온보딩 및 이후 요청에 Authorization: Bearer 헤더로 사용한다

5. 오류 코드 매핑

백엔드 코드사용자 메시지 (예시)프런트 행동
AUTH_KAKAO_STATE_MISMATCH"인증 세션이 만료되었습니다. 다시 시도해주세요."/login 이동, state 초기화
AUTH_KAKAO_CODE_EXPIRED"카카오 인증 코드가 만료되었습니다."재시도 버튼 노출
AUTH_KAKAO_AUTHENTICATION_FAILED"카카오 로그인에 실패했습니다."/login으로 복귀, 지원 링크 표시
AUTH_KAKAO_PROVIDER_DOWN"카카오 인증 서버가 응답하지 않습니다."재시도 유도 + 상태 페이지 링크
AUTH_KAKAO_ALREADY_LINKED"다른 계정에 연결된 카카오 계정입니다."관리자 문의 안내
KAKAO_PROVISIONING_SESSION_INVALID"프로비저닝 세션이 만료되었습니다."/login 이동 후 카카오 인증 재시도

6. 테스트 시나리오 (API 관점)

  1. GET /auth/oauth/kakao 호출 시 state 쿠키와 URL 반환 검증.
  2. 테스트용 Kakao code를 주입해 POST /auth/oauth/kakao/exchange 성공 응답 확인.
  3. state를 변조하여 400 AUTH_KAKAO_STATE_MISMATCH 응답 확인.
  4. Kakao sandbox 장애를 모킹하여 503 AUTH_KAKAO_PROVIDER_DOWN 처리 확인.
  5. 신규 사용자 플래그(requiresProfileSetup = true) 응답 후 appToken 기반 POST /auth/oauth/kakao/provisioning/complete 202 응답 확인.

7. 추후 고려 사항

  • 액세스 토큰 갱신 플로우 (POST /auth/token/refresh) 문서화
  • 로그아웃 및 Kakao unlink (POST /auth/oauth/kakao/unlink) 연동
  • 멀티 테넌트 환경에서 redirectUri 동적 결정 로직 명시
  • Next.js 15 Server Actions 기반 세션 재검증 API 설계 추가