Skip to content

feat(mobile): integrate backend authentication - #7

Draft
dohyeon16 wants to merge 3 commits into
refactor/feature-based-structurefrom
feature/mobile-auth-integration
Draft

feat(mobile): integrate backend authentication#7
dohyeon16 wants to merge 3 commits into
refactor/feature-based-structurefrom
feature/mobile-auth-integration

Conversation

@dohyeon16

@dohyeon16 dohyeon16 commented Aug 4, 2026

Copy link
Copy Markdown
Owner

개요 (Phase 2.5)

Phase 2 백엔드 인증 API를 모바일 앱(React Native/Expo)에 연동합니다. 이메일
회원가입·로그인·자동 로그인·토큰 갱신·로그아웃·회원정보·회원탈퇴를 구현하고,
기존 Google/Kakao/Naver 소셜 로그인 흐름은 그대로 보존합니다. Phase 3 기능
(근무지/출퇴근/급여/증빙/리포트)은 포함하지 않습니다.

구현 범위

  • 타입 안전 API 클라이언트 (src/core/api/): 공통 base URL, JSON, 타임아웃,
    네트워크/HTTP 오류 정규화, Authorization: Bearer, 개발 로그 secret redaction.
    refresh 엔드포인트에는 자동 재시도/interceptor를 걸지 않음(무한 루프 방지).
  • 인증 세션 계층 (src/features/auth/state/session.ts, 순수·테스트 가능):
    initialize/register/login/refreshSession/logout/getCurrentUser/updateCurrentUser/
    deleteCurrentUser/clearLocalSession.
  • 화면 연동: Splash(세션 복원 + 소셜/로컬 폴백), Login(이메일 로그인),
    Signup(가입 성공 시 자동 로그인), Account(회원정보 조회/이름 수정/회원탈퇴),
    More(백엔드 사용자 표시 + 세션 인지 로그아웃).

사용한 실제 API 엔드포인트 (backend 770416e, /api/v1 확인)

메서드 경로 용도
POST /api/v1/auth/register 회원가입 → 201 TokenPair (409 중복)
POST /api/v1/auth/login 로그인 → 200 (401 통합 실패 메시지)
POST /api/v1/auth/refresh 토큰 갱신 → 200 (401 무효/재사용 시 family 폐기)
POST /api/v1/auth/logout 로그아웃(멱등, 인증 불필요) → 200 {ok}
GET /api/v1/users/me 회원정보 (Bearer)
PATCH /api/v1/users/me 이름 수정 (Bearer)
DELETE /api/v1/users/me 회원탈퇴 → 204

응답 user: {id, email(nullable), name, primary_provider, created_at, updated_at}
내부 필드(password_hash 등) 미노출.

SecureStore / 토큰 정책

  • refresh 토큰: expo-secure-store(iOS Keychain / Android Keystore)에만 저장.
  • access 토큰: 메모리에만 보관(AsyncStorage 평문 저장 안 함).
  • 앱 시작 시 SecureStore refresh 토큰으로 access 재발급 → /users/me → 인증 전환.
  • 웹(개발 프리뷰)은 SecureStore 미지원이라 메모리 폴백(새로고침 시 재로그인).
  • 토큰 전체값을 로그/에러 객체에 넣지 않음.

refresh rotation / single-flight

  • refresh 성공 시 기존 refresh를 새 값으로 즉시 교체(rotation).
  • 동시 401이 몰려도 refresh는 한 번만 실행(single-flight), 대기 요청은 새 access로 1회 재시도.
  • 재시도도 401이면 세션 정리 후 로그인 화면 유도.
  • refresh가 401(재사용/폐기)이면 즉시 로컬 세션 삭제 + unauthenticated 전환.
  • 네트워크/타임아웃 오류는 세션을 지우지 않고 그대로 유지·전파.

기존 소셜 로그인 보존

  • Expo Go OAuth 브릿지/네이티브 SDK 흐름(POST /auth/session/{provider} 폴링,
    callback, DELETE 정리), Render callback URL, provider 환경변수, 포트 8081,
    prompt=login(카카오) 등 변경 없음.
  • 소셜 브릿지는 아직 Phase 2 토큰을 발급하지 않고 프로필만 반환하므로, 이메일
    인증 연동과 분리해 기존 로컬 Account 저장 방식을 그대로 유지했습니다.
    (후속: 백엔드 POST /api/v1/auth/bridge/exchange로 브릿지 세션→토큰 교환이
    가능하므로, 소셜 로그인을 동일 토큰 세션으로 통합하는 것을 다음 단계로 남깁니다.)

테스트 결과

  • npx tsc --noEmit: 통과 (strict).
  • npm test: 37/37 통과 (신규 인증 17 + 기존 20). 오류 정규화, 세션 복원,
    refresh rotation, 동시 401 single-flight, 재시도 후 401 세션 정리, refresh 재사용/폐기,
    로그아웃 로컬 정리, DELETE 204 처리, 탈퇴 후 상태 초기화 커버.
  • Expo 설정 검증(expo config) + web 번들 export: 통과.

Preview 통합 스모크 (Preview 전용, 임시 계정, 토큰 미출력)

https://workproof-backend-preview.onrender.com 대상 13/13 PASS:
register(201) → users/me → refresh(회전) → PATCH 이름 → logout → 폐기 refresh 재사용(401)
→ 재로그인 → DELETE(204) → 탈퇴 후 로그인 차단(401) → 탈퇴 후 access 차단(401).
임시 계정은 soft-delete 상태로 정리됨. Production에는 테스트 요청하지 않았습니다.

실기기 미검증 (별도 필요)

  • iPhone 실기기 / Expo Go 실기기 / Development Build
  • 네이티브 Keychain·Keystore 실제 저장 동작
  • Google/Kakao/Naver 실제 계정 로그인
  • 웹/네이티브 인터랙티브 화면 클릭 검증 (본 PR은 번들·export·API 통합까지 검증)

Issue #2를 닫지 않는 이유

Issue #2는 major enhancements의 네이티브/Development Build 검증 항목입니다. 본
작업은 그 검증을 대신 수행하지 않으며, 위 실기기 항목도 미검증이므로 Issue #2를
닫지 않습니다.

Merge 선행조건 / 순서

본 PR의 base는 PR #3 브랜치(refactor/feature-based-structure)입니다. 확정된 순서:

  1. Issue test: complete native validation for major enhancements #2 네이티브 검증
  2. PR feat: expand payroll, attendance, scheduling, and security features #1 Ready & merge
  3. PR #3를 main에 rebase, 재검증 & merge
  4. 그다음 본 PR 처리

backend / Render / Neon 무변경 확인


Phase 2.6 — Non-device stabilization (commit bfc5a59)

실기기 없이 검증 가능한 범위를 정적 분석·자동화 테스트·번들 검증·Preview API 통합으로 최대한 완성했습니다. 기기 의존 동작은 변경하지 않았습니다.

수정한 결함 (감사에서 발견, 실기기 불필요)

  • session.initialize(): SecureStore 읽기 실패/손상 토큰이 앱 시작을 크래시시키지 않도록 전체 guard → 안전하게 unauthenticated 폴백.
  • session.applySession(): refresh 토큰 저장 실패 시 메모리 access 토큰을 롤백(반쪽 인증 상태 방지).
  • session.clearLocalSession(): store.clear 실패를 best-effort 처리 → logout/탈퇴가 항상 unauthenticated에 도달.
  • API client: joinUrl로 base+/api/v1+path 결합 시 이중 슬래시 제거. fetch 주입 가능 구조로 바꿔 단위 테스트 가능하게(동작 동일).
  • notifications: 저장소 결합 피드에서 순수 deriveNotifications() 분리(동작 보존) → 알림 이동 대상/중복/정렬 자동 검증.

추가한 자동화 테스트 (+40, 총 60/60 통과)

  • API 클라이언트(10): URL 결합·이중 슬래시·헤더(Bearer/Content-Type)·204·비-JSON·오류 정규화·민감값 redaction·네트워크·타임아웃.
  • 세션(+6, 총 23): 빈/손상 토큰, SecureStore 읽기 예외, store.set 실패 시 미인증, single-flight 공유, store.clear 예외 로그아웃 — 기존 refresh rotation/동시 401/재사용 폐기/logout/DELETE 204 포함.
  • 알림 파생(8): 이동 대상(link/target·hasPay), 삭제된 근무지 skip, 빈 목록, 우선순위 정렬, read 반영.

정적/번들 검증

  • tsc --noEmit 통과 · npm test 60/60 · expo config OK.
  • iOS(1331 modules) · Android(1331) · Web JS 번들 export 전부 통과.
  • import 순환: 6건이나 전부 기존 코드(소셜 로그인 type-only 5 + 테마 1), 이번 추가 모듈은 순환 0.
  • expo-doctor 16/18 — 2건은 기존부터 있던 비차단 경고(@expo/config-plugins 직접설치 권고, expo 54.0.35↔~54.0.36 패치 드리프트). 이번 변경으로 새 경고 없음. (지시대로 expo install --fix/audit fix 미실행.)

Preview 통합 스모크 (Preview 전용, 임시계정, 토큰 미출력) — 19/19 PASS

health→register(201)→me→PATCH→중복(409)→오답(401)→미존재(401)→통합 실패 메시지→refresh(회전)→이전 refresh 재사용(401·family 폐기)→재로그인→logout→로그아웃 refresh 재사용(401)→재로그인→DELETE(204)→탈퇴 후 login/access/refresh 전부 401. 임시계정 soft-delete 정리. Production 무요청.

문서화한 발견사항 (이번엔 수정 안 함)

  • date.tstodayDateString/currentYearMonth가 UTC(toISOString) 기반 — KST 자정~09시 경계 하루 오차 가능. 단, 저장·비교에 동일 함수를 써 내부 일관(크래시/데이터손실 아님)하고, 근태 기록 날짜 지정이 Issue test: complete native validation for major enhancements #2 device 검증 대상이라 이 auth 브랜치에서 변경하지 않고 남깁니다.

실기기 미검증(BLOCKED — requires physical device/dev build)

Face ID 실제 프롬프트, Keychain/Keystore 실제 저장, GPS 센서, 로컬 알림 실수신, 파일 picker/공유 시트, 카메라/사진 권한, Safe Area 육안, Google/Kakao/Naver 실계정 로그인, custom scheme/deep-link 복귀. → PR #1#3#7 병합 정리 후 최종 1회 실기기 검증에서 확인. Issue #2 미close, PR #7 Draft 유지.


Phase 2.6 follow-up — 로컬 날짜 & 백업 복원 견고화 (commit fa4867d)

Phase 2.6 보고서의 비실기기 잔여 문제 2건을 정적 분석·자동화 테스트로만 해결.

1) date.ts UTC/KST 날짜 경계 오류

  • todayDateString/currentYearMonthDate.toISOString()(UTC) 기반이라 KST 00:00~08:59에 전날/전월 반환 → 순수 로컬 헬퍼 formatLocalDate/formatLocalYearMonth로 교체, new Date("YYYY-MM-DD") UTC 파싱 함정 회피용 parseLocalDate 추가.
  • 동일 안티패턴(toISOString().slice()를 로컬 날짜로 사용) 제거: payCalc.weekKeyOf(주 그룹핑 키 — behavior-neutral), backup 파일명 스탬프(native/web).
  • 타임스탬프(createdAt/updatedAt/addedAt/exportedAt)는 시점(instant)이라 ISO/UTC 그대로 유지 — 저장 형식 YYYY-MM-DD 불변, 기존 데이터 형식 변경 없음, timezone 라이브러리 미추가.

2) importAllData 복원 비원자성

  • 기존: multiRemove()multiSet() 순서라 중간 실패 시 데이터 유실/부분복원 위험.
  • 순수 restoreBackupData() 추출: 메모리 검증 → 스냅샷 → 새 값 먼저 쓰기 → 백업에 없는 키 비우기 → 실패 시 스냅샷으로 롤백. 롤백까지 실패하면 BackupRestoreError로 명확한 실패 반환(성공 처리 금지, reload 안 함). appLock·인증 토큰은 BACKUP_KEYS에 없어 항상 제외(SecureStore refresh·메모리 access·backend 세션은 백업 대상 자체가 아님).
  • AsyncStorage는 진짜 트랜잭션이 없으므로 "완전한 DB 트랜잭션"이 아니라 검증+스냅샷+배치쓰기+롤백의 atomic-like semantics임.

테스트 (+17, 총 77/77)

  • 로컬 날짜(9): 00:01/08:59/09:00 경계, 연말→연초, 윤년 2/29, parseLocalDate 로컬 파싱, round trip, today/yearMonth 일관성, 문자열 정렬/필터 회귀 — 시스템 TZ 비의존(Date 주입).
  • 복원(8): 정상 round trip, 비문자열/미지 키 무시, backupKeys 밖(appLock/토큰) 불가침, 첫 write 실패→롤백, 마지막 write 실패→롤백, 롤백까지 실패→rolledBack=false, 임의 문자열(예정근무/GPS/공제) 복원, 빈 백업.

검증

  • tsc --noEmit 통과 · npm test 77/77 · expo config OK · iOS/Android/Web export 통과 · git diff --check clean.
  • expo-doctor 16/18 — 기존 비차단 2건만(@expo/config-plugins 직접설치 권고: doctor도 peer 목적이면 무시 가능 안내 / expo 54.0.35↔~54.0.36 패치 드리프트, package.json 핀 ~54.0.2 미변경). 이번 변경으로 새 경고·새 의존성 없음. expo install --fix/audit fix 미실행.
  • Preview 스모크 재실행 19/19 PASS(임시계정 soft-delete 정리, Production 무요청).

실기기 미검증 유지 (BLOCKED)

기기 timezone 변경 실검증, 실제 파일 picker/공유 시트 복원 동작 등은 여전히 실기기 항목 — 최종 병합 정리 후 1회 실기기 검증에서 확인. Issue #2 미close, PR #7 Draft 유지.

🤖 Generated with Claude Code

도현 and others added 3 commits August 4, 2026 15:45
Connect the React Native app to the Phase 2 auth backend for email
signup/login/refresh/logout and account management, while leaving the
existing Google/Kakao/Naver social flows untouched.

- Add typed API client (core/api): timeout, HTTP error normalization,
  Bearer header, secret-redacted dev logging, /api/v1 base URL from
  EXPO_PUBLIC_API_BASE_URL (Preview fallback).
- Add auth session layer: refresh token in expo-secure-store, access
  token in memory, refresh rotation, single-flight refresh, one-shot
  401 retry, family-revoke handling. Pure/testable (session.ts).
- Wire Splash (restore session with social/local fallback), Login,
  Signup (auto-login on register), and add Account screen (view/edit
  name, delete account) + backend-aware logout in More.
- Unit tests for error normalization and session rotation/single-flight/
  401/logout/delete. Preview integration smoke passed 13/13.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EwR3pvvEE1hy9NNrmMjmCQ
Phase 2.6 non-device stabilization on the PR #7 integration branch.
Static analysis, automated tests, bundle validation, and Preview API
integration only — no device-dependent behavior changed.

Session robustness (features/auth/state/session.ts):
- initialize() now guards SecureStore read failures / corrupt tokens so a
  keychain error can't crash startup — falls back to unauthenticated.
- applySession() rolls back the in-memory access token if the refresh
  token can't be persisted (no half-authenticated state).
- clearLocalSession() treats store.clear() failures as best-effort so
  logout/delete always reach the unauthenticated state.

API client (core/api/client.ts):
- Inject fetch and drop DOM-global type coupling so the client is
  unit-testable under node:test.
- joinUrl() collapses duplicate slashes when combining base + /api/v1 + path.

Notifications (core/notifications):
- Extract pure deriveNotifications() from the storage-coupled feed so the
  notification targets / dedupe / sort logic is unit-testable
  (behavior-preserving).

Tests: +40 (session edge cases, API client, notification derivation);
node:test suite now 60/60. Preview integration smoke 19/19.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EwR3pvvEE1hy9NNrmMjmCQ
Phase 2.6 follow-up. Two non-device issues from the stabilization report,
fixed with static analysis + automated tests only.

Local dates (UTC/KST boundary):
- todayDateString/currentYearMonth used Date.toISOString() (UTC), which
  returns the previous day/month during KST 00:00–08:59. Replace with pure
  local-date helpers formatLocalDate/formatLocalYearMonth and add
  parseLocalDate to avoid the new Date("YYYY-MM-DD") UTC-parse trap.
- Remove the same toISOString().slice() antipattern from payCalc weekKeyOf
  (grouping key; behavior-neutral) and backup filename stamps.
- Timestamps (createdAt/updatedAt/addedAt/exportedAt) stay full ISO/UTC —
  those are instants, not local calendar dates.

Backup restore atomicity:
- importAllData did multiRemove() then multiSet(), so a mid-restore failure
  could wipe or partially restore data. Extract pure restoreBackupData()
  (validate in memory -> snapshot -> set-new then clear-absent -> rollback
  to snapshot on any failure). If rollback also fails, throw a typed
  BackupRestoreError instead of reporting success. appLock and auth tokens
  are never in BACKUP_KEYS, so they remain excluded.

Tests: +17 (9 local-date boundary, 8 restore failure-injection/rollback);
node:test suite now 77/77. tsc clean; iOS/Android/Web export pass; Preview
smoke 19/19. expo-doctor unchanged (2 pre-existing non-blocking warnings).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EwR3pvvEE1hy9NNrmMjmCQ
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant