사용자 계정 로그인 기능은 구글과 애플 소셜 로그인 구현으로 끝날 줄 알았다. 애플의 정책에서 애플 아이디를 이용하여 소셜 로그인을 구현할 경우, 애플 계정과 사용자가 쓰던 앱과의 회원 탈퇴 과정을 구현해야 앱 심사에서 거절당하지 않는다고 한다. 문제는 이 과정이 단순히 Xcode로만 끝나지 않고, Apple의 Token을 가져오는 기능을 데이터베이스에 구현해야 된다는 것이다. 3일 정도 시름을 한 끝에 구현을 성공하긴 했다.
Firebase에는 Cloud Function이라는 기능이 있다. Firebase 기능과 HTTPS 요청에 의해 트리거 되는 이벤트에 응답하여 백엔드 코드를 자동으로 실행할 수 있는 서버리스 프레임워크다. 이 환경을 구축하는 게 우리의 목표다.
1. 우선 Node.js와 npm(node package manager)을 설치해야한다. Node.js 사이트에 들어가 Node.js가 설치되면 npm도 같이 설치 되어있을 것이다.
확인방법
//terminal에서
$ node -v
$ npm -v
//firebase cloud function 설치
$ npm install -g firebase-tools
//firebase에 로그인 -> 웹으로 자동으로 연결되니 로그인하면 된다.
$ firebase login
//아래 명령어를 치기 전, 서버 기능을 구축할 프로젝트 폴더를 설정하고 해당 폴더로 이동 후 세팅 시작을 해야한다.
$ firebase init
입력하면 이런 화면이 뜬다.
여기서 화살표 위, 아래 키로 Functions 로 이동후 스페이스 바로 선택을 한 후, 엔터를 누른다.
Function setup에서는 JavaScript or TypeScript 선택, ESLint 선택, npm 설치 여부를 결정하는데, JS - yes - yes 를 고르면 된다. 그 후 잘 설치되었다면 위와 같은 화면이 뜬다.
VSCode 설치 및 functions 폴더 안에있는 index.js 를 VScode로 연다.
Cloud Functions 및 Admin SDK 모듈 주입
JsonWebToken(JWT) 모듈 설치
프로젝트의 functions 폴더로 이동 후 해당 명령어를 치면 된다. JWT는 애플 로그인 탈퇴를 위해 필요하다.
$ npm install jsonwebtoken
생성한 키는 .p8이라는 확장자명을 가지고 있다. 다운로드 한 후 Functions 폴더에 넣어주면 된다. 한 번 다운로드한 키는 절대 다시 받을 수 없으니, 주의하도록 하자!
AuthKey_XXXXXXXXXX.p8
부분은 위에서 다운받은 파일명을,
iss
: 'YOUR TEAM ID'
부분은 Apple 개발자 페이지에서 확인할 수 있고,
sub
: 'YOUR CLIENT ID'
는 등록된 앱의 Bundle Identifier (Bundle ID)를,
kid
: 'YOUR KEY ID'
는 'AuthKey_XXXXXXXXXX.p8'
에서 XXXXXXXXXX 부분을 가져다 채우면 된다.
Refresh Token 및 토큰을 이용해 auth 정보를 revoke 하는 함수 작성
your client id
는 앱 bundle ID
를 입력해주면 된다
작성한 코드를 Firebase 프로젝트에 적용
$ firebase deploy --only functions
참고로 이 과정에서 ESLint 관련 오류가 정말 많이 생긴다. 이 문제를 해결하는 데만, 거의 7~8시간이 걸린 것 같다. 정확한 방법은 모르지만, functions 폴더 내에 있는 package.json 파일에 들어가서 'eslint .'을 'eslint'로 고치면 해결됬었다.
이제 토큰키를 가져오고 토큰 해제만 하면 구현은 끝이다. 사실 상, 앞선 세팅이 어려운 거고 코드는 짧다.
// 로그인을 구현했던 VC에서
extension LoginVC : ASAuthorizationControllerDelegate, ASAuthorizationControllerPresentationContextProviding {
func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) {
// 앞에 적었던 코드들...
// 사용자의 authorizationCode를 로그인 시 미리 가져온다. 회원 탈퇴 시, 필요하기 때문이다.
if let authorizationCode = appleIDCredential.authorizationCode, let codeString = String(data: authorizationCode, encoding: .utf8) {
let url = URL(string: "https://us-central1-everydiary-a9c5e.cloudfunctions.net/getRefreshToken?code=\(codeString)".addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? "https://apple.com")!
let task = URLSession.shared.dataTask(with: url) {(data, response, error) in
if let data = data {
let refreshToken = String(data: data, encoding: .utf8) ?? ""
print(refreshToken)
UserDefaults.standard.set(refreshToken, forKey: "refreshToken")
UserDefaults.standard.synchronize()
}
}
task.resume()
}
}
authorizationCode를 가져오는 코드를 구현해주고, Firebase에서 Apple 사용자 계정을 탈퇴, 즉 token을 revoke 시키면 끝이난다.
// 로그아웃 & 회원탈퇴 VC에서
// Apple 계정 탈퇴
func deleteUserDataFromApple() {
let token = UserDefaults.standard.string(forKey: "refreshToken")
if let token = token {
let url = URL(string: "https://us-central1-everydiary-a9c5e.cloudfunctions.net/revokeToken?refresh_token=\(token)".addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? "https://apple.com")!
let task = URLSession.shared.dataTask(with: url) {(data, response, error) in
if let error = error {
print("Error:", error.localizedDescription)
return
}
// HTTP 응답 코드 확인
if let httpResponse = response as? HTTPURLResponse {
print("HTTP Status Code:", httpResponse.statusCode)
}
// 응답 데이터 확인
if let data = data, let utf8Text = String(data: data, encoding: .utf8) {
print("Response Data:", utf8Text)
}
}
task.resume()
}
// Firebase 회원 탈퇴
deleteUserDataFromFirebase()
// 마지막으로 Firebase 로그아웃
do {
try Auth.auth().signOut()
} catch let signOutError as NSError {
print("Error signing out: %@", signOutError)
}
}
코드 작성보다 외부 세팅이 너무나도 어려웠고, 참고할 만한 자료조차 제대로 없었다. 삽질을 제대로하고, 포기 직전이였지만 다행히 성공이 되었고 안도의 한숨을 쉬었다.