iosfirebaseswiftuifirebase-authentication

Firebase Authentication SwiftUI: Email verification status becomes true after password change


I'm using Firebase Authentication in my iOS app and have implemented email verification during user registration. I also check for unverified emails upon sign-in and require users to verify their email before accessing the app, which is working as expected. However, I've noticed an unexpected behavior:

  1. User signs up with email and password but doesn't verify their email.
  2. User is not allowed to enter the app due to the email verification check.
  3. User resets password without verifying email.
  4. After changing the password, the user can access the app because isEmailVerified becomes true.

I expected isEmailVerified to remain false after a password change since email verification and password change are separate processes. The user should still be required to verify their email before accessing the app. Is this behavior expected in Firebase Authentication?

Code:

@MainActor
    func signInWithEmail() async {
        isLoading = true
        
        validateEmail()
        validatePassword()
        
        if emailCredentials.email.isEmpty {
            emailValidationState.email = .invalid("Email is required")
            isLoading = false
            return
        }
        if emailCredentials.password.isEmpty {
            emailValidationState.password = .invalid("Password is required")
            isLoading = false
            return
        }
        
        guard case .valid = emailValidationState.email,
              case .valid = emailValidationState.password else {
            isLoading = false
            return
        }
        
        do {
            let result = try await authService.signInWithEmail(
                email: emailCredentials.email,
                password: emailCredentials.password
            )
            print("BBBB \(result.user.isEmailVerified)")
            if !result.user.isEmailVerified {
                emailVerificationState.credentials = AuthenticationModel.CreateAccountCredentials(
                    email: emailCredentials.email,
                    password: emailCredentials.password
                )
                emailVerificationState.email = emailCredentials.email
                try await result.user.sendEmailVerification()
                emailAuthAlert.message = AppConstants.Alert.Message.format(
                    AppConstants.Alert.Message.emailVerificationSent,
                    with: emailCredentials.email
                )
                emailAuthAlert.showAlert = true
                
            } else {
                handleSuccessfulAuthentication(for: .email)
            }
            
        } catch {
            handleAuthError(error, for: .emailAuth)
        }
        
        isLoading = false
    }

@MainActor
func resetPassword() async {
    isLoading = true
    
    validateResetPasswordEmail()
    
    if resetPasswordCredentials.email.isEmpty {
        resetPasswordValidationState.email = .invalid("Email is required")
        isLoading = false
        return
    }
    
    guard case .valid = resetPasswordValidationState.email else {
        isLoading = false
        return
    }
    
    do {
        try await authService.resetPassword(email: resetPasswordCredentials.email)
        forgotPasswordAlert.message = AppConstants.Alert.Message.format(
            AppConstants.Alert.Message.passwordResetSent,
            with: resetPasswordCredentials.email
        )
        
        forgotPasswordAlert.showAlert = true
    } catch {
        handleAuthError(error, for: .forgotPassword)
    }
    
    isLoading = false
}

Solution

  • The emailVerified flag in Firebase authentication indicates whether it has been verified that the user owns the email address; nothing more, nothing less. Given that, them clicking the link in a password-reset email verifies that they have access to that email address, so constitutes email verification.

    So to your question: yes, it is expected that emailVerified is set to true after the user reset their password through an email-link.


    The problem you're having comes from the fact that you're trying to add your own meaning to the emailVerified property, specifically whether you created a document for that user in Firestore.

    To prevent this sort of mismatch, I recommend not using emailVerified for this purpose. Rather, whenever you need to load the user's profile document from Firestore, first check if such a document exists, and if not: send them through the registration flow that creates the document.