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:
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
}
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.