-
Notifications
You must be signed in to change notification settings - Fork 0
Auth Flow
사용자가 앱에 접속하여 로그인/회원가입하는 전체 과정을 설명합니다.
| 항목 | 내용 |
|---|---|
| 인증 방식 | 카카오 로그인 (OAuth 2.0) |
| 토큰 관리 | accessToken (30분) + refreshToken (14일, 쿠키) |
| 상태 관리 | Zustand + AsyncStorage |
사용자가 앱을 실행했을 때의 분기 로직입니다.
┌─────────────────────────────────────────────────────────────────┐
│ 앱 시작 플로우 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ [앱 시작] │
│ │ │
│ ▼ │
│ Zustand persist → AsyncStorage에서 상태 복원 │
│ │ │
│ ▼ │
│ accessToken 있음? │
│ │ │
│ ├─→ ✅ 있음 │
│ │ │ │
│ │ ▼ │
│ │ userType에 따라 메인 화면 분기 │
│ │ │ │
│ │ ├─→ WORKER → [WorkerHome] │
│ │ └─→ EMPLOYER → [EmployerHome] │
│ │ │ │
│ │ └─→ 401 발생 시 → refresh 시도 │
│ │ │ │
│ │ ├─→ 성공 → 새 토큰 저장 → 계속 사용 │
│ │ └─→ 실패 → [로그인 화면] │
│ │ │
│ └─→ ❌ 없음 │
│ │ │
│ ▼ │
│ isOnboardingCompleted 확인 │
│ │ │
│ ├─→ true → [로그인 화면] (온보딩 스킵) │
│ │ │
│ └─→ false → [온보딩 화면] │
│ │ │
│ ▼ │
│ "시작하기" 버튼 │
│ │ │
│ ▼ │
│ [로그인 화면] │
│ │
└─────────────────────────────────────────────────────────────────┘
| 시나리오 | 온보딩 | 로그인 | 이동 화면 |
|---|---|---|---|
| 앱 최초 설치 후 첫 실행 | ✅ 표시 | 필요 | 온보딩 → 로그인 |
| 회원가입 완료 후 앱 재시작 | ❌ 스킵 | 자동 | 메인 화면 |
| 14일 후 토큰 만료 시 | ❌ 스킵 | 필요 | 로그인 화면 |
| 로그아웃 후 | ❌ 스킵 | 필요 | 로그인 화면 |
| 앱 삭제 후 재설치 | ✅ 표시 | 필요 | 온보딩 → 로그인 |
카카오 로그인 버튼 클릭 후의 처리 과정입니다.
┌─────────────────────────────────────────────────────────────────┐
│ 카카오 로그인 플로우 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ [카카오 로그인 버튼 클릭] │
│ │ │
│ ▼ │
│ 카카오 SDK 호출 → kakaoAccessToken 획득 │
│ │ │
│ ▼ │
│ 백엔드 POST /api/auth/kakao/login { kakaoAccessToken } │
│ │ │
│ ├─→ 200 OK + status="LOGGED_IN" (기존 회원) │
│ │ │ │
│ │ ▼ │
│ │ accessToken 저장 (AsyncStorage) │
│ │ refreshToken 저장 (쿠키) │
│ │ userInfo 저장 (userId, name, userType) │
│ │ │ │
│ │ ▼ │
│ │ userType에 따라 메인 화면 분기 │
│ │ │ │
│ │ ├─→ WORKER → [WorkerHome] │
│ │ └─→ EMPLOYER → [EmployerHome] │
│ │ │
│ ├─→ 200 OK + status="WITHDRAWN_PENDING" (탈퇴 보류 계정) │
│ │ │ │
│ │ ▼ │
│ │ [WithdrawnAccountSheet] (§3-1 참고) │
│ │ │
│ └─→ 404 USER_NOT_FOUND (신규 회원) │
│ │ │
│ ▼ │
│ signUpStore.mode = "register" │
│ kakaoAccessToken을 signUpStore에 임시 저장 │
│ │ │
│ ▼ │
│ [회원가입 화면] (STEP 1) │
│ │
└─────────────────────────────────────────────────────────────────┘
💡 카카오 로그인 응답은 2026-05 이후
KakaoLoginResult구조로 변경됐다.status필드(LOGGED_IN/WITHDRAWN_PENDING)로 분기하며, 신규 사용자는 기존처럼 404로 떨어진다.
회원 탈퇴는 즉시 삭제가 아니라 soft delete + 30일 유예 방식이다. 유예 기간 안에 같은 카카오 계정으로 다시 로그인을 시도하면 사용자에게 "복구" 또는 "새 계정으로 가입" 두 가지 선택을 안내한다.
┌─────────────────────────────────────────────────────────────────┐
│ 탈퇴 보류 계정 처리 플로우 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ [/kakao/login → status="WITHDRAWN_PENDING"] │
│ │ │
│ │ 응답에 withdrawnAccount 포함: │
│ │ • name │
│ │ • userType ("WORKER" | "EMPLOYER") │
│ │ • withdrawnAt (탈퇴 시각) │
│ │ • profileImageUrl │
│ │ │
│ ▼ │
│ [WithdrawnAccountSheet] 표시 │
│ │ │
│ ├─→ "기존 계정 복구" │
│ │ │ │
│ │ ▼ │
│ │ POST /api/auth/kakao/restore │
│ │ { kakaoAccessToken } │
│ │ │ │
│ │ ├─→ 200 OK │
│ │ │ └─→ User.deletedAt = null │
│ │ │ accessToken/refreshToken 발급 │
│ │ │ → 자동 로그인 → 메인 화면 │
│ │ │ │
│ │ └─→ 실패 → 에러 안내 → 시트 닫기 │
│ │ │
│ └─→ "새 계정으로 가입" │
│ │ │
│ ▼ │
│ signUpStore.mode = "purge-and-register" │
│ signUpStore.kakaoAccessToken = (현재 토큰) │
│ │ │
│ ▼ │
│ [회원가입 화면] (STEP 1부터 재입력) │
│ │ │
│ ▼ │
│ [STEP 4] 완료 시점에 분기: │
│ POST /api/auth/kakao/purge-and-register │
│ │ (기존 탈퇴 계정 + 산하 데이터 영구 삭제 후 │
│ │ 신규 사용자로 가입, 트랜잭션으로 원자성 보장) │
│ ▼ │
│ 새 accessToken/refreshToken 발급 → 로그인 → 메인 │
│ │
└─────────────────────────────────────────────────────────────────┘
엔드포인트: POST /api/auth/kakao/restore
Request Body:
{ "kakaoAccessToken": "string" }Response Body (성공 시): LoginResponse와 동일 구조(accessToken/refreshToken/userId/name/userType)
| 상태 | 의미 |
|---|---|
| 200 OK | 복구 성공 — User.deletedAt이 null로 복구되고 토큰 발급 |
| 400 USER_NOT_WITHDRAWN | 탈퇴 상태가 아닌 계정에 호출 |
| 404 USER_NOT_FOUND | 등록되지 않은 카카오 계정 |
⚠️ 복구는 탈퇴 시 보존된UserSettings만 그대로 유지한다. 사업장/계약/근무기록은 자동 복구되지 않는다 (다른 사용자 데이터 일관성 보호).
엔드포인트: POST /api/auth/kakao/purge-and-register
Request Body: /api/auth/kakao/register와 동일 (kakaoAccessToken/name/userType/phone/bankName/accountNumber/profileImageUrl)
Response Body (성공 시): LoginResponse와 동일 구조
| 상태 | 의미 |
|---|---|
| 200 OK | 기존 탈퇴 계정 hard delete + 신규 가입 성공 |
| 400 DUPLICATE_KAKAO_ACCOUNT | 정상 상태 계정에 호출 (탈퇴 계정에만 사용 가능) |
백엔드는 한 트랜잭션 안에서 hard delete → register를 순차 처리하며, register 실패 시 hard delete도 함께 롤백되어 데이터 영구 손실을 방지한다.
-
signUpStore의mode필드("register" | "purge-and-register")가 STEP 4 알람 화면에서 어느 API를 호출할지 결정한다. -
SignUpNavigator마운트 시에는reset()이 아닌resetForm()을 호출해 mode를 보존한다. 폼 필드만 초기화하며, mode/kakaoAccessToken은 호출자(WelcomeScreen)가 사전 설정한 값을 유지한다. - 일반 신규 가입(404 분기)에서도
setMode("register")를 명시 호출해 이전 세션의 mode 잔여를 방지한다.
신규 사용자의 회원가입 5단계 과정입니다.
┌─────────────────────────────────────────────────────────────────┐
│ 회원가입 플로우 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ [STEP 1/5] 회원유형 선택 │
│ │ │
│ ├─→ "근로자" 선택 → userType: "WORKER" │
│ └─→ "사장님" 선택 → userType: "EMPLOYER" │
│ │ │
│ ▼ │
│ [STEP 2/5] 프로필 사진 (선택) │
│ │ │
│ ├─→ 이미지 선택 → 400x400 리사이즈 → 압축 → base64 인코딩 │
│ └─→ "나중에 설정하기" → 스킵 │
│ │ │
│ ▼ │
│ [STEP 3/5] 기본정보 입력 │
│ │ │
│ │ • 이름 (필수) │
│ │ • 핸드폰 번호 (010-XXXX-XXXX) │
│ │ • 은행명 (모달 선택) - 근로자만 │
│ │ • 계좌번호 - 근로자만 │
│ │ │
│ ▼ │
│ [STEP 4/5] 알람 설정 + 회원가입 API 호출 │
│ │ │
│ ├─→ "알람 허용" → 시스템 권한 요청 → API 호출 │
│ └─→ "나중에 설정하기" → API 호출 │
│ │ │
│ ▼ │
│ 백엔드 POST /api/auth/kakao/register 호출 │
│ │ │
│ ├─→ 성공 │
│ │ │ │
│ │ ▼ │
│ │ accessToken 저장 (AsyncStorage) │
│ │ userInfo 저장 (userId, name, userType) │
│ │ │ │
│ │ ▼ │
│ │ [STEP 5/5] 가입완료 화면 │
│ │ │
│ └─→ 실패 (400 등) │
│ │ │
│ ▼ │
│ 에러 Toast 표시 │
│ signUpStore 초기화 │
│ [로그인 화면]으로 이동 │
│ │
│ [STEP 5/5] 가입완료 │
│ │ │
│ │ signUpStore 초기화 │
│ │ │
│ ├─→ WORKER → "시작하기" → [WorkerHome] │
│ └─→ EMPLOYER → "매장 관리하러 가기" → [WorkplaceManage] │
│ │
└─────────────────────────────────────────────────────────────────┘
엔드포인트: POST /api/auth/kakao/register
Request Body:
{
"kakaoAccessToken": "string",
"name": "string",
"userType": "WORKER",
"phone": "010-1234-5678",
"bankName": "string",
"accountNumber": "string",
"profileImageUrl": "base64 문자열 또는 빈 문자열"
}| 필드 | 타입 | 필수 | 설명 |
|---|---|---|---|
| kakaoAccessToken | string | ✅ | 카카오 로그인 토큰 |
| name | string | ✅ | 사용자 이름 |
| userType | string | ✅ | "WORKER" 또는 "EMPLOYER" |
| phone | string | ✅ | 핸드폰 번호 (하이픈 포함) |
| bankName | string | ❌ | 은행명 (근로자만 필수) |
| accountNumber | string | ❌ | 계좌번호 (근로자만 필수) |
| profileImageUrl | string | ❌ | base64 인코딩된 이미지 |
Response Body (성공 시):
{
"success": true,
"data": {
"accessToken": "string",
"userType": "WORKER",
"userId": 123,
"name": "홍길동"
}
}| 토큰 | 저장 위치 | 수명 | 관리 방식 |
|---|---|---|---|
| accessToken | AsyncStorage + Zustand | 30분 | 클라이언트 직접 관리 |
| refreshToken | 쿠키 (HttpOnly) | 14일 | 서버가 Set-Cookie로 전송 |
| kakaoAccessToken | signUpStore (임시) | - | 회원가입 완료 시 삭제 |
┌─────────────────────────────────────────────────────────────────┐
│ 토큰 갱신 플로우 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ [API 요청] │
│ │ │
│ ▼ │
│ Authorization: Bearer {accessToken} │
│ │ │
│ ├─→ 200 OK → 정상 응답 │
│ │ │
│ └─→ 401 Unauthorized (토큰 만료) │
│ │ │
│ ▼ │
│ CookieManager.get(baseURL)로 쿠키 직접 조회 │
│ → Cookie 헤더에 수동 첨부 (RN은 자동 전송 미지원) │
│ │ │
│ ▼ │
│ POST /api/auth/refresh (Cookie 헤더에 refreshToken) │
│ │ │
│ ├─→ 200 OK │
│ │ │ │
│ │ ▼ │
│ │ 새 accessToken 저장 │
│ │ 원래 API 요청 재시도 │
│ │ │
│ └─→ 401/403 (refreshToken도 만료) │
│ │ │
│ ▼ │
│ logoutCallback() 호출 │
│ → 저장된 토큰/쿠키 삭제 │
│ → [로그인 화면]으로 이동 │
│ │
└─────────────────────────────────────────────────────────────────┘
React Native 환경은 Axios의 withCredentials: true만으로는 쿠키가 자동 전송되지 않는다. 이에 따라 갱신 인터셉터에서 쿠키를 수동으로 읽어 헤더에 첨부하는 구조를 사용한다.
| 이슈 | 이전 구현 | 현재 구현 |
|---|---|---|
| 리프레시 요청 시 쿠키 전송 |
withCredentials: true만 사용 → 쿠키 미전송 |
CookieManager.get(baseURL)로 조회 후 Cookie 헤더에 수동 첨부 |
| 갱신 실패 시 화면 이동 | 콜백 미등록 → 빈 UI |
RootNavigator에서 isNavReady 이후 setLogoutCallback 등록 |
| 로그아웃 API 실패 처리 | 에러 Toast 표시 + 갇힘 | Toast 제거, finally 블록이 화면 이동 보장 |
📝 향후 계획: PR #65(OPEN)에서
@react-native-cookies/cookies의존성 제거 후 refreshToken을 body로 전송하는 방식으로 전환 예정.
authStore (AsyncStorage persist):
interface AuthState {
accessToken: string | null;
userInfo: UserInfo | null; // { userId, name, userType }
isLoggedIn: boolean;
isHydrated: boolean;
}onboardingStore (AsyncStorage persist):
interface OnboardingState {
isOnboardingCompleted: boolean;
isHydrated: boolean;
}signUpStore (persist 없음 - 임시 저장):
type SignUpMode = "register" | "purge-and-register";
interface SignUpState {
kakaoAccessToken: string | null;
mode: SignUpMode; // 회원가입 분기 (탈퇴 계정 재가입 식별)
userType: "WORKER" | "EMPLOYER" | null;
profileImageUri: string | null;
profileImageBase64: string | null;
name: string;
phone: string;
bankName: string;
accountNumber: string;
}
mode는 STEP 4에서kakaoRegisterWithTokenvskakaoPurgeAndRegisterWithToken분기에 사용된다.resetForm()은 폼 필드만,reset()은 전체(mode 포함) 초기화.
┌─────────────────────────────────────────────────────────────────┐
│ 데이터 흐름 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 1. 앱 시작 │
│ └─→ AsyncStorage에서 Zustand로 상태 복원 (authStore) │
│ └─→ AsyncStorage에서 Zustand로 상태 복원 (onboardingStore) │
│ └─→ 쿠키에서 refreshToken 자동 로드 │
│ │
│ 2. 앱 사용 중 │
│ └─→ Zustand Store에서 읽기/쓰기 (빠른 접근) │
│ │
│ 3. 중요한 변경 시 (로그인/로그아웃) │
│ └─→ AsyncStorage에 자동 동기화 (persist) │
│ │
│ 4. 앱 종료 │
│ └─→ Zustand 초기화됨 (메모리) │
│ └─→ AsyncStorage, 쿠키는 유지됨 │
│ │
└─────────────────────────────────────────────────────────────────┘
| 화면 | 설명 | 관련 Issue |
|---|---|---|
| 온보딩 (3페이지) | 앱 소개 스와이프 화면 | #1 |
| 로그인 (Welcome) | 카카오 로그인 버튼 | #1 |
| 회원가입 STEP 1 | 회원유형 선택 | #2 |
| 회원가입 STEP 2 | 프로필 사진 | #2 |
| 회원가입 STEP 3 | 기본정보 입력 | #2 |
| 회원가입 STEP 4 | 알람 설정 + API 호출 | #2 |
| 회원가입 STEP 5 | 가입완료 | #2 |
# 네비게이션
npm install @react-navigation/native @react-navigation/native-stack
# 인증/저장
npm install @react-native-cookies/cookies
npm install @react-native-async-storage/async-storage
npm install @react-native-seoul/kakao-login
npm install zustand
# 이미지 처리
npm install expo-image-picker
npm install expo-image-manipulator
# 알림
npm install expo-notifications
npm install expo-device
# 기타
npm install react-native-alert-notification| 메서드 | 엔드포인트 | 설명 |
|---|---|---|
| POST | /api/auth/kakao/login |
카카오 로그인 (status: LOGGED_IN / WITHDRAWN_PENDING 분기) |
| POST | /api/auth/kakao/register |
카카오 회원가입 |
| POST | /api/auth/kakao/restore |
탈퇴 계정 복구 (deletedAt=null) |
| POST | /api/auth/kakao/purge-and-register |
탈퇴 계정 영구 삭제 후 신규 가입 |
| POST | /api/auth/refresh |
토큰 갱신 |
| POST | /api/auth/logout |
로그아웃 |
| DELETE | /api/auth/withdraw |
회원 탈퇴 (soft delete + 30일 유예) |