EveryDiary - 로그인서비스 (3) Apple Revoke Token

ulls12·2024년 3월 7일
0

Swift TIL

목록 보기
50/60

사용자 계정 로그인 기능은 구글과 애플 소셜 로그인 구현으로 끝날 줄 알았다. 애플의 정책에서 애플 아이디를 이용하여 소셜 로그인을 구현할 경우, 애플 계정과 사용자가 쓰던 앱과의 회원 탈퇴 과정을 구현해야 앱 심사에서 거절당하지 않는다고 한다. 문제는 이 과정이 단순히 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
  1. 이제 터미널에서 명령어를 이용하여 frebase를 설치하고 초기세팅을 해줄 것이다. 오류가 날경우 npm 앞에 sudo를 붙여 관리자 권한으로 설치해보자.
//firebase cloud function 설치
$ npm install -g firebase-tools

//firebase에 로그인 -> 웹으로 자동으로 연결되니 로그인하면 된다.
$ firebase login

//아래 명령어를 치기 전, 서버 기능을 구축할 프로젝트 폴더를 설정하고 해당 폴더로 이동 후 세팅 시작을 해야한다.
$ firebase init

입력하면 이런 화면이 뜬다.
여기서 화살표 위, 아래 키로 Functions 로 이동후 스페이스 바로 선택을 한 후, 엔터를 누른다.

Function setup에서는 JavaScript or TypeScript 선택, ESLint 선택, npm 설치 여부를 결정하는데, JS - yes - yes 를 고르면 된다. 그 후 잘 설치되었다면 위와 같은 화면이 뜬다.

  1. VSCode 설치 및 functions 폴더 안에있는 index.js 를 VScode로 연다.

  2. Cloud Functions 및 Admin SDK 모듈 주입

  3. JsonWebToken(JWT) 모듈 설치
    프로젝트의 functions 폴더로 이동 후 해당 명령어를 치면 된다. JWT는 애플 로그인 탈퇴를 위해 필요하다.

$ npm install jsonwebtoken
  1. 애플 개발자 사이트에서 Keys 를 추가로 생성해준다.

생성한 키는 .p8이라는 확장자명을 가지고 있다. 다운로드 한 후 Functions 폴더에 넣어주면 된다. 한 번 다운로드한 키는 절대 다시 받을 수 없으니, 주의하도록 하자!

index.js 세팅

  1. JWT(JSON Web Token)를 반환하는 함수
    Firebase를 이용한 애플 로그인 시, ASAuthorizationAppleIDCredential 이라는 객체를 받아온다. 이 credential에서 authorization code를 이용하여, Apple에 REST API 통신을 하여 refresh token을 받아올 수 있다.

AuthKey_XXXXXXXXXX.p8 부분은 위에서 다운받은 파일명을,
iss: 'YOUR TEAM ID' 부분은 Apple 개발자 페이지에서 확인할 수 있고,
sub: 'YOUR CLIENT ID' 는 등록된 앱의 Bundle Identifier (Bundle ID)를,
kid: 'YOUR KEY ID''AuthKey_XXXXXXXXXX.p8' 에서 XXXXXXXXXX 부분을 가져다 채우면 된다.

  1. Refresh Token 및 토큰을 이용해 auth 정보를 revoke 하는 함수 작성

    your client id는 앱 bundle ID를 입력해주면 된다

  2. 작성한 코드를 Firebase 프로젝트에 적용

$ firebase deploy --only functions

참고로 이 과정에서 ESLint 관련 오류가 정말 많이 생긴다. 이 문제를 해결하는 데만, 거의 7~8시간이 걸린 것 같다. 정확한 방법은 모르지만, functions 폴더 내에 있는 package.json 파일에 들어가서 'eslint .'을 'eslint'로 고치면 해결됬었다.

Xcode에서 코드 구현

이제 토큰키를 가져오고 토큰 해제만 하면 구현은 끝이다. 사실 상, 앞선 세팅이 어려운 거고 코드는 짧다.

// 로그인을 구현했던 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)
        }
    }

코드 작성보다 외부 세팅이 너무나도 어려웠고, 참고할 만한 자료조차 제대로 없었다. 삽질을 제대로하고, 포기 직전이였지만 다행히 성공이 되었고 안도의 한숨을 쉬었다.

profile
I am 개발해요

0개의 댓글