deprecated된 firebase dynamic link 없이 Firebase email link authentication 구현하기 - 2

DoDoBest

·

2024. 9. 16. 07:06

이전 글

https://dodobest.tistory.com/120

 

deprecated된 firebase dynamic link 없이 Firebase email link authentication 구현하기 - 1

2025년 8월 25일부터 Firebase dynamic Link(FDL)가 deprecated 되어 사용할 수 없게 됩니다. Deprecation 문서에 따르면 신규 프로젝트 생성자는 FDL을 사용할 수 없고, Firebase email link authentication에 한해서 고객센

dodobest.tistory.com

 

사진: Unsplash 의 Amanda Dalbjörn

 

이전 글에서 사용자가 입력한 이메일로 인증 메일을 발송하는 코드를 작성했습니다.

이번 글에서는 인증 메일의 확인 여부를 관찰하는 코드를 작성해보겠습니다.

Android Kotlin 기반으로 작성되었습니다.

 

Firebase 인증 관련 알아야 할 개념

 

Firebase Authentication에 계정을 새롭게 생성했다면 Firebase.auth.currentUser의 이메일 인증 여부(isEmailVerified)는 false 입니다.

Firebase.auth.currentUser 정보의 갱신 주기는 1시간 입니다. 따라서 currentUser의 reload 함수를 호출해 user 정보를 수동으로 갱신해야 합니다.

 

사용자가 A 이메일의 계정을 사용하고 있고, B 이메일 계정으로 변경하고자 B 이메일로 인증 메일을 전송한 상황을 생각해봅시다.

이때, currentUser는 여전히 A 이메일 계정 정보를 가리키며 reload 함수를 호출하더라도 동일합니다. A 이메일에 대한 인증을 완료한 상태라면 이메일 인증 여부는 B 이메일 인증 여부와 관계 없이 true를 반환합니다. 따라서 이메일 인증 여부 뿐만 아니라 currentUser의 이메일이 B 이메일을 가리키는지도 확인해야 합니다.

사용자가 B 이메일 인증을 완료한 후, reload 함수를 다시 호출하면 currentUser는 null이 됩니다.

이메일 인증 여부를 확인하기 위해 signInWithEmailAndPassword 함수를 호출해야 합니다.

그러면 currentUser는 B 이메일 계정 정보를 가리키며, 이메일 인증 여부를 확인할 수 있습니다.

 

인증 state 확인하기

 

인증 여부를 확인하기 위해 CallbackFlow를 사용했습니다. CallbackFlow는 관찰자가 없으면 데이터를 생성하지 않는 cold Flow 입니다. 이점을 활용해서 이메일의 인증 여부를 갱신하는 reload 함수를 호출할 수 있습니다.

화면이 Paused 상태로 전환되어 관찰이 중단되었다가 화면이 Resumed 상태로 돌아오면 CallbackFlow에 대한 관찰을 다시 시작하고 데이터를 새롭게 생성합니다. 이때 이메일의 인증 여부를 확인하기 전 reload 함수를 호출하면 최신 인증 상태를 확인할 수 있습니다.

사용자가 앱에서 인증을 요청을 한 후, 브라우저 또는 이메일 앱으로 전환해 인증을 완료한 후 원래 앱으로 돌아오는 경우가 여기에 해당합니다.

 

private var renewAuth: (isVerified: Boolean) -> Unit = {}

fun getEmailAuthState(email: String): Flow<Boolean> {
    return callbackFlow {
        val authStateListener = FirebaseAuth.AuthStateListener {
            // 이메일 인증 여부에 대한 최신 정보를 얻기 위해 reload 함수를 반드시 호출해야 한다.
            Firebase.auth.currentUser?.reload()?.addOnCompleteListener {
                val isVerified = Firebase.auth.currentUser?.let { user ->
                    user.email == email && user.isEmailVerified
                } ?: false
                trySend(isVerified)
            }
        }
        renewAuth = { isVerified -> trySend(isVerified) }

        Firebase.auth.addAuthStateListener(authStateListener)
        awaitClose {
            Firebase.auth.removeAuthStateListener(authStateListener)
        }
    }
}

 

사용자가 앱에서 인증을 요청한 후, 화면을 전환하지 않고 PC 혹은 태블렛과 같은 다른 기기에서 이메일을 인증하는 경우에는 Paused 상태로 전환되지 않습니다.

따라서 이런 경우에는 reload 함수를 직접 호출해 갱신해야 합니다(또는 1시간 주기의 자동 갱신을 기다려야 합니다).

이 역할을 하는 변수가 renewAuth입니다. callbackFlow는 Flow 외부에서도 값을 emit 할 수 있습니다.

 

일정 시간 주기로 reload 함수가 호출되도록 설정할 수도 있지만, 사용자가 직접 인증 상태를 갱신하도록 버튼을 만들었습니다.

인증 상태 갱신 버튼은 인증 메일이 정상적으로 발송된 경우에만 보이도록 visibility를 설정했습니다. (아래 preview에서는 tools를 visible로 설정했기 때문에 보이고 있습니다.) 

 

 

인증 상태 갱신 버튼이 눌리면 인증 상태를 확인한 후 renewAuth를 통해 반환하도록 설정했습니다.

다른 이메일에서 인증을 완료하는 경우 currentUser는 null이 되거나 FirebaseAuthInvalidUserException을 던집니다. 이때, 인증을 완료한 이메일로 로그인했습니다.

suspend fun renewAuthState(email: String, password: String) {
    // 다른 이메일로 변경하면 currentUser는 null이 되기 때문에 재로그인 필요
    try {
        Firebase.auth.currentUser?.reload()?.await() ?: run {
            Firebase.auth.signInWithEmailAndPassword(email, password).await()
        }
    } catch (_: FirebaseAuthInvalidUserException) {
        try {
            Firebase.auth.signInWithEmailAndPassword(email, password).await()
        } catch (_: FirebaseAuthInvalidUserException) {
            renewAuth(false)
            return
        }
    }

    val isVerified = Firebase.auth.currentUser?.let { user ->
        user.email == email && user.isEmailVerified
    } ?: false
    renewAuth(isVerified)
}

 

 

이와 관련된 전체 코드는 아래 commit에서 확인할 수 있습니다.

 

https://github.com/DoTheBestMayB/Zipbab/commit/9422f2806403e765dbf1369c5c25e3485f847a77

 

feat: 이메일 인증 메일 발송 및 인증 상태 확인 로직 구현(FDL 없이) · DoTheBestMayB/Zipbab@9422f28

DoTheBestMayB committed Sep 15, 2024

github.com