Kakao OAuth API 연동 계약 (웹 프런트엔드)
본 문서는 SleepZ 웹 프런트엔드가 Kakao OAuth 로그인/회원가입을 구현할 때 참조해야 할 백엔드 API 계약을 정리한 것입니다. 자세한 도메인 스펙은 docs/domains/kr/core-domains/auth/endpoints.md 및 관련 문서를 우선 참조합니다.
1. 연동 개요
- 대상 백엔드:
apps/dha-sleep-api - 관련 도메인 문서:
/domains/kr/core-domains/auth/endpoints/domains/kr/core-domains/auth/business-rules.md/domains/common/core-domains/auth/endpoints.md
- 프런트엔드 역할:
- 인가 URL 요청 → state 쿠키 수신
- Kakao 권한 페이지로 리다이렉트
- 콜백에서 code/state 확인 후 백엔드에 교환 요청
- 발급된 JWT를 HttpOnly 쿠키로 수신, 이후 보호된 페이지 접근
- (신규 사용자) AppToken을 이용해 온보딩 폼 제출, 운영 승인 후 재로그인
⚠️ 사전 조건: 모든 Kakao 교환 및 온보딩 요청은
POST /auth/app/challenge→POST /auth/app/complete-challenge앱 인증 흐름을 통과해appToken을 발급받은 상태여야 합니다.
2. 엔드포인트 요약
| 단계 | HTTP | 엔드포인트 | 인증 | 설명 |
|---|---|---|---|---|
| 인가 URL 초기화 | GET | /auth/oauth/kakao | Public | state 생성, Kakao 인가 URL 제공 |
| Kakao 콜백 처리 | GET | /auth/oauth/kakao/callback | Public | code/state 검증, 토큰 교환, 최종 리다이렉트 |
| 토큰 교환 헬퍼 (웹) | POST | /auth/oauth/kakao/exchange | Public | 프런트 전용. 콜백에서 받은 code를 백엔드에 전달해 JWT 또는 프로비저닝 티켓 획득 |
| 온보딩 제출 | POST | /auth/oauth/kakao/provisioning/complete | App Token | appToken 기반으로 온보딩 폼 데이터를 제출하고 프로비저닝 세션을 완료 |
| (기존 사용자) 프로필 업데이트 | PUT | /users/profile | Access 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_MISMATCH400 AUTH_KAKAO_CODE_EXPIRED503 AUTH_KAKAO_PROVIDER_DOWN
- 프런트 처리
- 기존 사용자 → JWT 쿠키를 설정하고
/dashboard로 이동 - 신규 사용자 → appToken 쿠키(
sleepz_app_token)를 유지하고/onboarding/profile로 이동 401또는403→ 토스트 노출 후/login으로 이동503→ 재시도 안내 메시지, Contact 지원 링크 포함
- 기존 사용자 → JWT 쿠키를 설정하고
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_FIELD401 AUTH_TOKEN_EXPIRED
- 프런트 처리
401→ 토큰 갱신 로직 준비 전까지/login으로 복귀
4. 상태 검증 및 보안 고려 사항
| 항목 | 프런트 처리 |
|---|---|
state 검증 | 백엔드에서 쿠키 값과 비교. 프런트는 추가 조치 불필요하지만, 쿠키 삭제 금지 |
state TTL | 10분 초과 시 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 관점)
GET /auth/oauth/kakao호출 시 state 쿠키와 URL 반환 검증.- 테스트용 Kakao code를 주입해
POST /auth/oauth/kakao/exchange성공 응답 확인. - state를 변조하여
400 AUTH_KAKAO_STATE_MISMATCH응답 확인. - Kakao sandbox 장애를 모킹하여
503 AUTH_KAKAO_PROVIDER_DOWN처리 확인. - 신규 사용자 플래그(
requiresProfileSetup = true) 응답 후 appToken 기반POST /auth/oauth/kakao/provisioning/complete202 응답 확인.
7. 추후 고려 사항
- 액세스 토큰 갱신 플로우 (
POST /auth/token/refresh) 문서화 - 로그아웃 및 Kakao unlink (
POST /auth/oauth/kakao/unlink) 연동 - 멀티 테넌트 환경에서
redirectUri동적 결정 로직 명시 - Next.js 15 Server Actions 기반 세션 재검증 API 설계 추가