Skip to content

fix(auth): Keychain 토큰 저장 실패 전파 (조용한 로그아웃 방지) - #110

Merged
gomminjae merged 1 commit into
developfrom
fix/keychain-save-propagation
Jul 4, 2026
Merged

gomminjae merged 1 commit into
developfrom
fix/keychain-save-propagation

Conversation

@gomminjae

Copy link
Copy Markdown
Collaborator

문제

TokenStore.saveAccessToken이 KeychainStorage.save의 반환값(성공 여부)을 버렸다. Keychain 저장이 실패하면:

  • 인메모리엔 토큰이 남아 이번 세션은 로그인 상태처럼 보임
  • 하지만 다음 실행 때 Keychain에 없어 조용히 로그아웃

fix/stability-hardening(#107)이 save를 -> Bool로 바꿔뒀지만, 콜러가 그 값을 안 썼다. 이 PR이 콜러까지 연결한다.

변경

  • TokenStorageProtocol.saveAccessToken → @discardableResult -> Bool
  • TokenStore: 저장 성공 시에만 인메모리 반영 → 세션 상태가 실제 저장과 어긋나지 않음. 로그아웃/삭제는 보안상 인메모리 항상 클리어
  • LoginError.tokenPersistenceFailed 추가
  • AuthRepositoryImpl.login/signup: 저장 실패 시 throw → UI가 로그인 실패로 처리·재시도 가능. (login은 저장 로직을 do/catch 밖으로 빼 에러가 networkError로 삼켜지지 않게)

검증

  • tuist generate + xcodebuild build-for-testing 성공
  • AuthTests 통과

🤖 Generated with Claude Code

기존엔 saveAccessToken이 Keychain 결과를 버려서, 저장 실패 시 이번 세션은
로그인 상태처럼 보이지만 다음 실행 때 토큰이 사라져 조용히 로그아웃됐다.

- TokenStorageProtocol.saveAccessToken → @discardableResult -> Bool
- TokenStore: 저장 성공 시에만 인메모리 반영(세션 상태=실제 저장 일치). 로그아웃은 인메모리 항상 클리어
- LoginError.tokenPersistenceFailed 추가
- AuthRepositoryImpl.login/signup: 저장 실패 시 throw (login은 저장을 do/catch 밖으로 이동해 에러가 삼켜지지 않게)
- AuthTests 목 시그니처 동기화

검증: build-for-testing + AuthTests 통과.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request updates the token storage mechanism to return a boolean indicating success or failure when saving or deleting an access token. It also updates the authentication repository to handle token persistence failures by throwing a new tokenPersistenceFailed error. The review feedback suggests refactoring the saveAccessToken method in TokenStore.swift to use a guard let statement instead of if let, which reduces nesting and improves code readability in accordance with Swift style guidelines.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines 40 to 57
if let token {
KeychainStorage.save(token, key: Key.accessToken)
let saved = KeychainStorage.save(token, key: Key.accessToken)
if saved {
lock.withLock { state in
state.token = token
state.loaded = true
}
}
return saved
} else {
KeychainStorage.delete(key: Key.accessToken)
let deleted = KeychainStorage.delete(key: Key.accessToken)
// 로그아웃/삭제 시 보안상 인메모리는 항상 비운다 (Keychain 삭제 실패와 무관).
lock.withLock { state in
state.token = nil
state.loaded = true
}
return deleted
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

if let token과 else 블록을 사용하는 대신, guard let token else 패턴을 사용하면 중첩(nesting)을 줄이고 "Happy Path"(토큰 저장 성공 흐름)를 함수의 최상위 레벨에 유지할 수 있어 Swift 스타일 가이드에 더 부합하며 가독성이 향상됩니다.

Suggested change
if let token {
KeychainStorage.save(token, key: Key.accessToken)
let saved = KeychainStorage.save(token, key: Key.accessToken)
if saved {
lock.withLock { state in
state.token = token
state.loaded = true
}
}
return saved
} else {
KeychainStorage.delete(key: Key.accessToken)
let deleted = KeychainStorage.delete(key: Key.accessToken)
// 로그아웃/삭제 시 보안상 인메모리는 항상 비운다 (Keychain 삭제 실패와 무관).
lock.withLock { state in
state.token = nil
state.loaded = true
}
return deleted
}
guard let token else {
let deleted = KeychainStorage.delete(key: Key.accessToken)
// 로그아웃/삭제 시 보안상 인메모리는 항상 비운다 (Keychain 삭제 실패와 무관).
lock.withLock { state in
state.token = nil
state.loaded = true
}
return deleted
}
let saved = KeychainStorage.save(token, key: Key.accessToken)
if saved {
lock.withLock { state in
state.token = token
state.loaded = true
}
}
return saved
References
  1. Swift API Design Guidelines 및 일반적인 Swift 스타일 가이드에서는 조기 탈출(early exit)을 위해 guard 문을 사용하고, 함수의 주요 흐름(happy path)을 최소한의 들여쓰기로 유지하는 것을 권장합니다. (link)

@gomminjae
gomminjae merged commit 94f34c0 into develop Jul 4, 2026
1 check passed
@gomminjae
gomminjae deleted the fix/keychain-save-propagation branch July 4, 2026 06:04
gomminjae added a commit that referenced this pull request Jul 4, 2026
CI가 여태 테스트를 안 돌려(SwiftLint만) 숨어 있던 결함들:
- ProjectHelper: 모든 Testing 타깃에 .shared 추가 (SearchTesting이 Shared 참조하나 미선언 → 빌드 실패)
- LoginViewModel: handleLoginError switch에 .tokenPersistenceFailed 처리 추가 (PR #110에서 케이스 추가 후 non-exhaustive 회귀)
- MypageTests: StubTokenStorage.saveAccessToken을 -> Bool로 동기화 (PR #110 프로토콜 변경 미반영)

로컬 tuist test --platform ios 전체 통과 확인.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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