Final (20)
Apple 로그인 개선
모델링
1
2
3
4
5
6
7
8
9
import Foundation
struct AppleTokenResponse: Codable {
let access_token: String?
let expires_in: Int?
let id_token: String?
let refresh_token: String?
let token_type: String?
}
배포전 여러 자료를 찾아보다가, 애플 계정 관련해서는 토큰이 있어야 한다는것을 보고 구현을 해보려 한다. 참고글을 보면서 만들었다.
✅ refreshToken 저장
Apple 로그인 성공 후 authorizationCode
를 이용해 Apple 서버로 POST 요청을 보내 refresh_token
을 획득한다.
예시:
1
2
3
4
5
6
7
8
9
10
11
if let code = appleCredential.authorizationCode,
let codeString = String(data: code, encoding: .utf8) {
self.getAppleRefreshToken(code: codeString) { refreshToken in
guard let refreshToken = refreshToken else {
completion(.failure(...))
return
}
UserDefaults.standard.set(refreshToken, forKey: "appleRefreshToken")
completion(.success(result))
}
}
✅ JWT 발급 (makeJWT)
Apple 서버로 토큰을 요청하거나 revoke할 때 필요한 client_secret
은 JWT로 생성해야 한다. 내부적으로는 다음과 같은 필드로 구성된다:
- iss: Apple Team ID
- sub: App Bundle ID
- aud: https://appleid.apple.com
예시:
1
2
3
4
5
6
7
8
9
10
11
let myClaims = MyClaims(
iss: "LA95MXQ3R5",
iat: iat,
exp: exp,
aud: "https://appleid.apple.com",
sub: "com.TeamSwiftbreakers.TteoPpoKki4U"
)
let privateKey = try Data(contentsOf: url)
let jwtSigner = JWTSigner.es256(privateKey: privateKey)
let signedJWT = try myJWT.sign(using: jwtSigner)
Apple 탈퇴: refreshToken revoke 흐름
Apple 사용자는 refresh_token을 직접 revoke 해줘야 한다.
예시:
1
2
3
4
5
6
7
8
9
10
guard let refreshToken = UserDefaults.standard.string(forKey: "appleRefreshToken") else {
completion(.failure(...))
return
}
let clientSecret = self.makeJWT()
self.revokeAppleToken(clientSecret: clientSecret, token: refreshToken) {
self.deleteUserFromDatabase(uid: user.uid, completion: completion)
}
내부 요청 URL은 다음과 같다:
https://appleid.apple.com/auth/revoke?client_id=com.TeamSwiftbreakers.TteoPpoKki4U&client_secret=…&token=…&token_type_hint=refresh_token
Google 로그인: 토큰 저장
Google 로그인 성공 후, 추후 탈퇴에 사용할 토큰을 저장한다:
1
2
3
4
5
6
7
8
googleSignIn(result: GIDSignInResult, completion: ...) {
let idToken = result.user.idToken?.tokenString ?? ""
let accessToken = result.user.accessToken.tokenString
UserDefaults.standard.set(idToken, forKey: "googleIDToken")
UserDefaults.standard.set(accessToken, forKey: "googleAccessToken")
...
}
Google 탈퇴: 재인증 후 삭제
Firebase는 user.delete()
호출 전에 최근 인증이 필요하다.
Google 사용자는 reauthenticate
과정을 반드시 거쳐야 탈퇴가 가능하다.
예시:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
guard let idToken = UserDefaults.standard.string(forKey: "googleIDToken"),
let accessToken = UserDefaults.standard.string(forKey: "googleAccessToken") else {
completion(.failure(...))
return
}
let credential = GoogleAuthProvider.credential(withIDToken: idToken, accessToken: accessToken)
user.reauthenticate(with: credential) { _, error in
if let error = error {
completion(.failure(error))
} else {
self.performDelete(user: user, completion: completion)
}
}
회원 탈퇴 전체 흐름 (deleteCurrentUser
)
회원탈퇴는 다음과 같은 분기 흐름으로 구성됨:
1
2
3
4
5
6
7
8
9
10
11
12
if let user = Auth.auth().currentUser {
for provider in user.providerData {
switch provider.providerID {
case "apple.com":
// Apple refreshToken revoke 후 삭제
case "google.com":
// Google reauthenticate 후 삭제
default:
break
}
}
}
데이터베이스 및 사용자 삭제
Firebase Auth와 Realtime Database에서 사용자 정보를 함께 삭제한다.
1
2
3
4
5
6
7
ref.child("users").child(uid).removeValue { error, _ in
if let user = Auth.auth().currentUser {
user.delete { error in
...
}
}
}
변경 요약 표
기능 | 리팩토링 전 | 리팩토링 후 |
---|---|---|
Apple refreshToken 저장 | ❌ 없음 | ✅ authorizationCode로 요청 후 저장 |
Apple 토큰 revoke | ❌ 없음 | ✅ revoke API 직접 호출 |
Google 탈퇴 재인증 | ❌ 없음 | ✅ reauthenticate + delete |
JWT 서명 | ❌ 없음 | ✅ .p8 키 기반 client_secret 생성 |
회원탈퇴 흐름 | 단순 delete | ✅ provider별 분기 + 보안 처리 |
결론
이번 리팩토링을 통해 다음과 같은 실질적인 문제가 해결되었다:
- Apple 인증 후 토큰 저장/관리 문제
- Google 탈퇴 시
auth/requires-recent-login
에러 - 사용자 탈퇴 전 인증 프로세스 통합 처리
- 외부 OAuth 시스템과 Firebase의 연결을 보안적으로 안전하게 구성
당시 문제 해결 과정에서 공식 문서나 기존 라이브러리만으로는 흐름을 파악하기 어려웠고, GPT를 통한 시도도 명확하지 않아 시행착오가 많았다.
현재 구조는 이 경험을 바탕으로 앱 서비스 운영에 적합하도록 정리된 결과물이다.