1. 서두

토이 프로젝트에서 아래 라이브러리를 사용해 소셜 로그인을 구현하고 있다.

이 글에서는 애플 소셜 로그인에서도 탈퇴(앱과 연결 끊기) 기능 구현을 정리한다. 탈퇴는 사용자에게 당연히 제공되어야 하는 사용자의 권리다. 뭐 필수 기능이니 구현하는 데 어려울 건 없을 것이다. 아마 라이브러리에 탈퇴하기() 같은 메서드가 있을 것이고 우리는 그걸 호출하기만 하면 될 것이다. 아주 쉽다. 자 시작해보자. ...어? 아니네?

1.1. 엥? 로그인은? 로그아웃은? 구글은?

애플 소셜 로그인/로그아웃, 구글 소셜 로그인/로그아웃/탈퇴 기능 구현에는 특별한 이슈가 없다. 개발자가 상상하고 있는 거의 그대로 메서드가 나와있고 우리는 호출하기만 되는 형태다. 일부 콜백을 사용해야 하긴 하지만 큰 문제는 아니다. 따라서 일단은 이슈가 있다고 생각되는 애플 쪽의 탈퇴 기능을 먼저 정리한다. (나머지 기능들은 나중에 정리할 수도 있다.) 일단은 공식문서들을 참고하시라.

2. 구현

2.1. onCredentialRevoked()

일단 @invertase/react-native-apple-authentication 라이브러리의 README 를 찬찬히 살펴봐도 탈퇴를 위한 메서드는 존재하지 않는다. (구글 쪽 라이브러리에서는 revokeAccess()라는 메서드를 제공하는 것과 대조적이다.) 그나마 탈퇴 쪽과 관련된 메서드라면 onCredentialRevoked(listener) 가 있다. 이 메서드는 사용자가 탈퇴되었을 때 실행되는 콜백을 등록하게 해준다.

여기서 평소 iOS 를 사용하지 않는 사람들은 당황할 수 있다 (내가 그랬다). 탈퇴 기능은 제공하지 않지만 탈퇴 상태를 들을 수 있는 이벤트 리스너만 제공하고 있는 것이다. 그럼 대체 탈퇴는 어떻게 하는 거야?

다행히 답은 멀리 있지 않다. 애플은 iOS 기기 내에서 특정 앱과의 연결을 끊을 수 있는 (탈퇴할 수 있는) 기능을 제공한다. (apple.com 에서도 제공하는 것으로 알고 있다. 확인해보지는 않았다.)

iOS 기기의 [설정] 앱 - 최상단의 자신의 이름 영역 선택 - [암호 및 보안] - [Apple ID를 사용하는 앱] - 탈퇴할 앱 선택 - [Apple ID 사용 중단]

이 말은 즉슨, 탈퇴 기능을 직접 구현할 필요는 없다는 것이다. 우리는 사용자가 애플을 통해 탈퇴를 했을 때 그에 대응하는 처리만 해주면 된다.

구현 예

그럼 문서에 나와있는대로 탈퇴 기능을 구현해보자.

function App() {
  // ...

  useEffect(() => {
    // 현재 기기가 애플 로그인을 지원하는 기기인지 확인
    if(!appleAuth.isSupported) {
      return;
    }

    const unsubscriber = appleAuth.onCredentialRevoked(() => {
      // 사용자의 정보를 가지고 탈퇴 기능을 수행하자.
      doSomethingForRevoke();
      // firebase 에서도 로그아웃
      auth().signOut();
    });
    return unsubscriber;
  }, []);

  // ...

  return (
    <View>
    </View>
  )
}

});

2.2. getCredentialStateForUser()

좋다. 구현이 완료되었다. 라이브러리의 README 에는 예외 처리에 대한 안내가 없는 걸로 봐서 이 콜백은 사용자의 탈퇴 시 무조건 실행을 보장하는 것 같다 (API 문서를 보고 싶지만 링크가 깨져있다).

그렇게 믿고 싶다. 하지만 세상에 백퍼센트는 없다. 특히 앱 프로세스가 완전히 죽은 상태에서 탈퇴를 하고 앱을 실행했을 때 onCredentialRevoked() 가 실행되지 않는 것을 여러차례 직접 목격한 나로서는, 더더욱 무시할 수 없었다. 이 콜백이 실행되지 않을 때를 대비해야 한다.

이럴 때 쓸 수 있는 메서드가 getCredentialStateForUser(user)다. 이 메서드는 현재 사용자의 상태를 반환한다.

enum AppleCredentialState {
  REVOKED = 0, // The Opaque user ID was revoked by the user.
  AUTHORIZED = 1, // The Opaque user ID is in good state.
  NOT_FOUND = 2, // The Opaque user ID was not found.
  TRANSFERRED = 3, // N/A
}

즉 사용자가 앱에 진앱했을 때, 혹은 사용자 정보가 업데이트되었다고 추정될 때, 이 메서드로 사용자의 상태를 확인하면 탈퇴 여부를 확인할 수 있다는 말이다.

구현 예

여기서는 "사용자의 인증 정보가 바뀐 시점"을 React Native Firebase 의 auth().onAuthStateChanged() 콜백으로 확인한다. 만약 React Native Firebase 를 사용하지 않는다면 다른 방법으로 해당 시점을 찾아야 할 것이다.

function App() {
  // ...

  useEffect(() => {
    const subscribe = auth().onAuthStateChanged(async user => {
      if(!user) {
        // 로그아웃된 상태 처리
        loggedOut();
        return;
      }

      const isApple = (
        appleAuth.isSupported
        && user.providerData[0].providerId === 'apple.com'
      );
      const uidInProvider = user.providerData[0].uid;
      const appleState = await appleAuth.getCredentialStateForUser(uidInProvider);

      if(isApple && appleState === appleAuth.State.REVOKED) {
        // 사용자의 정보를 가지고 탈퇴 기능을 수행하자.
        revokeThisService();
        // firebase 에서도 로그아웃
        auth().signOut();
      }
    })
  }, []);

  // ...

  return (
    <View>
    </View>
  )
}

});

3. 결론

마지막으로 요약하자. React Native 에서 @invertase/react-native-apple-authentication 라이브러리를 사용해 탈퇴 기능을 구현할 때는 아래 두 개의 메서드를 적절히 잘 조합하면 되겠다.

  • onCredentialRevoked()
  • getCredentialStateForUser()