Firebase email verification always return false user not verified. iOS - ios

I am implementing firebase email verification, but when user received an email it's redirecting to app which is fine. When I am trying to sign in with verified email Auth.auth().currentUser?.isEmailVerified always return false. Below is my code:
class ViewController: UIViewController {
#IBOutlet weak var emailTextField: UITextField!
#IBOutlet weak var signInBtn: UIButton!
var link: String!
override func viewDidLoad() {
super.viewDidLoad()
if let link = UserDefaults.standard.value(forKey: "Link") as? String{
self.link = link
signInBtn.isEnabled = true
}
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
self.view.endEditing(true)
}
#IBAction func didTapSignInWithEmailLink(_ sender: AnyObject) {
if let email = self.emailTextField.text {
Auth.auth().signIn(withEmail: email, link: self.link) { (user, error) in
if let error = error {
print("\(error.localizedDescription)")
return
}
}
} else {
print("Email can't be empty")
}
print(Auth.auth().currentUser?.email)
if Auth.auth().currentUser?.isEmailVerified == true {
print("User verified")
} else {
print("User not verified")
}
}
#IBAction func didTapSendSignInLink(_ sender: AnyObject) {
if let email = emailTextField.text {
let actionCodeSettings = ActionCodeSettings()
actionCodeSettings.url = URL(string: "https://example.firebaseapp.com")
// The sign-in operation has to always be completed in the app.
actionCodeSettings.handleCodeInApp = true
actionCodeSettings.setIOSBundleID(Bundle.main.bundleIdentifier!)
actionCodeSettings.setAndroidPackageName("com.example.android",
installIfNotAvailable: false, minimumVersion: "12")
Auth.auth().sendSignInLink(toEmail: email, actionCodeSettings: actionCodeSettings) { error in
if let error = error {
print("\(error.localizedDescription)")
return
}
print("Check your email for link")
}
} else {
print("Email cant be empty")
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}

I've found the solution we need to reload the user, which will update the current user status. Call Auth.auth.currentUser.reload in didTapSignInWithEmailLink button func:
Auth.auth().currentUser?.reload(completion: { (error) in
if error == nil{
if let email = self.emailTextField.text {
Auth.auth().signIn(withEmail: email, link: self.link) { (user, error) in
if let error = error {
print("\(error.localizedDescription)")
return
}
}
} else {
print("Email can't be empty")
}
print(Auth.auth().currentUser?.email)
if Auth.auth().currentUser?.isEmailVerified == true {
print("User verified")
} else {
print("User not verified")
}
} else {
print(error?.localizedDescription)
}
})

Related

google sign in sign in flow canceled iOS

I'm writing an iOS app that has to use google sign in. It compiles fine but when it runs and I try to sign in with a button, the sign in flow is interrupted:
This is the error: Optional("The operation couldn’t be completed. (com.google.GIDSignIn error -4.)")
error with signing in: Optional(Error Domain=com.google.GIDSignIn Code=-5 "The user canceled the sign-in flow." UserInfo={NSLocalizedDescription=The user canceled the sign-in flow.})
In the simulator, when I press the button, it will show the dialog box asking whether it is ok for the app to access google and then it will just disappear after a second or two.
Here is my ViewController:
//
// ViewController.swift
// frcscout
//
// Created by Elliot Scher on 12/20/22..
//
import UIKit
import GoogleSignIn
import GTMSessionFetcher
import GoogleAPIClientForREST
class ViewController: UIViewController {
#IBOutlet weak var signInButton: UIButton!
private let service = GTLRSheetsService()
override func viewDidLoad() {
super.viewDidLoad()
googleSignIn { signInStatus in
if signInStatus == true {
self.signInButton?.setTitle("Sign out", for: .normal)
print("Signed in!")
} else {
self.signInButton?.setTitle("Sign in", for: .normal)
print("Issues with signing in...")
}
}
}
#IBAction func signInPressed(_ sender: UIButton) {
print("Sign in pressed")
let user = GIDSignIn.sharedInstance.currentUser
if (user == nil) {
googleSignIn() { success in
if success == true {
sender.setTitle("Sign out", for: UIControl.State.normal)
} else {
return
}
}
} else {
GIDSignIn.sharedInstance.signOut()
self.service.authorizer = nil
sender.setTitle("Sign in", for: UIControl.State.normal)
}
}
}
extension ViewController {
func googleSignIn(completionHandler: #escaping (Bool) -> Void) {
GIDSignIn.sharedInstance.restorePreviousSignIn { user, error in
if error == nil {
print("Managed to restore previous sign in!\nScopes: \(String(describing: user?.grantedScopes))")
self.requestScopes(googleUser: user!) { success in
if success == true {
completionHandler(true)
} else {
completionHandler(false)
}
}
} else {
print("No previous user!\nThis is the error: \(String(describing: error?.localizedDescription))")
let signInConfig = GIDConfiguration.init(clientID: K.clientID)
GIDSignIn.sharedInstance.signIn(with: signInConfig, presenting: self) { gUser, signInError in
if signInError == nil {
self.requestScopes(googleUser: gUser!) { signInSuccess in
if signInSuccess == true {
completionHandler(true)
} else {
completionHandler(false)
}
}
} else {
print("error with signing in: \(String(describing: signInError)) ")
self.service.authorizer = nil
completionHandler(false)
}
}
}
}
}
func requestScopes(googleUser: GIDGoogleUser, completionHandler: #escaping (Bool) -> Void) {
let grantedScopes = googleUser.grantedScopes
if grantedScopes == nil || !grantedScopes!.contains(K.grantedScopes) {
let additionalScopes = K.additionalScopes
GIDSignIn.sharedInstance.addScopes(additionalScopes, presenting: self) { user, scopeError in
if scopeError == nil {
user?.authentication.do { authentication, err in
if err == nil {
guard let authentication = authentication else { return }
// Get the access token to attach it to a REST or gRPC request.
// let accessToken = authentication.accessToken
let authorizer = authentication.fetcherAuthorizer()
self.service.authorizer = authorizer
completionHandler(true)
} else {
print("Error with auth: \(String(describing: err?.localizedDescription))")
completionHandler(false)
}
}
} else {
completionHandler(false)
print("Error with adding scopes: \(String(describing: scopeError?.localizedDescription))")
}
}
} else {
print("Already contains the scopes!")
completionHandler(true)
}
}
}
Can anyone help me fix this? Thanks!!

Only instance methods can be declared #IBAction error?

I am facing this error on build for the function shown in the code
Only instance methods can be declared #IBAction
this error is coming up only after I introduced google sign in method for similar functionality , earlier it not an error
#IBAction func SignInButtonAction(_ sender: Any) {
guard let email = emailField.text else { return }
guard let pass = passwordField.text else { return }
Auth.auth().signIn(withEmail: email, password: pass) { user, error in
if error == nil && user != nil {
let setupcheckref = Firestore.firestore().collection("users").document(Auth.auth().currentUser!.uid)
setupcheckref.getDocument{(document, error) in
if let document = document, document.exists{
let dataDescription = document.data().map(String.init(describing:)) ?? "nil"
self.checksetup = document.get("setupComplete") as! Bool
if self.checksetup == true {
if Auth.auth().currentUser!.isEmailVerified {
self.performSegue(withIdentifier: "toLoginFeed", sender: self)
}
else{
print("please verify your email")
try! Auth.auth().signOut()
}
}
else{
self.view.makeToast("Please Setup Your Account!", duration: 2.5)
self.performSegue(withIdentifier: "fromlogintosetup", sender: self)
SVProgressHUD.dismiss()
} }
}
// self.dismiss(animated: false, completion: nil)
} else {
print("Error logging in: \(error!.localizedDescription)")
// self.resetForm()
// SVProgressHUD.dismiss()
}
}
}
That means you can create #IBActions only as instance methods of a class.
You might be creating it of a class.
class VC: UIViewController {
#IBAction func SignInButtonAction(_ sender: Any) {
//your code...
}
}

Firebase Email Verification Swift

I tried to use the Firebase Authentication, but in the login lets me in even when I haven't used the email to confirm the verification.
I have two Viewcontroller , one for Login and the other for signUP.
I can log in and I get the email for verification, but I can also log in in without verification.
public func sendVerificationMail() {
if self.authUser != nil && !self.authUser!.isEmailVerified {
self.authUser!.sendEmailVerification(completion: { (error) in
// Notify the user that the mail has sent or couldn't because of an error.
})
} else {
// Either the user is not available, or the user is already verified.
}
}
#IBAction func signupButtonTapped(_ sender: Any) {
print("Sign up button tapped")
Auth.auth().createUser(withEmail: self.userEmailTextField.text!, password: self.userPasswordTextField.text!) { (user, error) in
if user != nil {
print("User has Signed Up")
self.sendVerificationMail()
}
if error != nil {
print("User cant Sign Up")
}
}
}
#IBAction func signinButtonTapped(_ sender: Any) {
Auth.auth().signIn(withEmail: self.userEmailTextField.text!, password: self.userPasswordTextField.text!) { (user, error) in
if user != nil {
print("User has Signed In")
}
if error != nil {
print("Cant Sign in user")
} else {
self.performSegue(withIdentifier: "toHome", sender: nil)
}
}
}
Firebase Auth doesn't prevent people from signing in when their email isn't verified. If you want to prevent users from advancing when they aren't verified, you will need to code this on the client using the isEmailVerified boolean.
Auth.auth().signIn(withEmail: self.userEmailTextField.text!, password: self.userPasswordTextField.text!) { (authResult, error) in
if let authResult = authResult {
let user = authResult.user
print("User has Signed In")
if user.isEmailVerified {
self.performSegue(withIdentifier: "toHome", sender: nil)
} else {
// do whatever you want to do when user isn't verified
}
}
if let error = error {
print("Cant Sign in user")
}
}
A more comprehensive solution than that provided by #jen-person (and based on together with another SO answer) is this:
final class MySignInView: UIView { // or possibly MySignInViewController: UIViewController
// ... your properties etc...
#IBAction func signInButtonPressed(sender _: AnyObject) {
trySigningIn()
}
}
// MARK: Private
private extension MySignInView {
func trySigningIn() {
guard
let email = userEmailTextField.text,
let password = userPasswordTextField.text
else {
print("Cannot sign in, email or password is 'nil'")
return
}
do {
try signIn(email: email, password: password) { [unowned self] authResult in
self.userDidSignIn(authResult.user)
}
} catch {
// Display error
}
}
func userDidSignIn(_ user: FIRUser) {
// Creds to Jen: https://stackoverflow.com/a/51389154/1311272
guard user.isEmailVerified else {
// TODO display message about non verified email user?
return
}
performSegue(withIdentifier: "toHome", sender: nil)
}
}
// MARK: Error
private extension MySignInView {
enum Error: Strng, Equatable {
case invalidEmail
case userDisabled
case wrongPassword
case userNotFound
case networkError
case unknownError
}
}
// MARK: Firebae specific
private extension MySignInView {
func signIn(
email: String,
password: String,
onSuccessful: (AuthDataResult) -> Void
) throws {
Auth.auth().signIn(
withEmail: email,
password: password
) { (authResult, anyError) in
if let anyError = anyError {
if let error = Error(anyError: anyError) {
throw error
} else {
fatalError("Unsupported error: \(anyError)")
}
}
onSuccessful(authResult)
}
}
}
private extension MySignInView.Error {
init?(anyError: Swift.Error) {
guard let authErrorCode = FIRAuthErrorCode(rawValue: anyError.code) else {
return nil
}
self.init(fireBaseAuthErrorCode: authErrorCode)
}
// Creds goes to to: https://stackoverflow.com/a/39936083/1311272
init(fireBaseAuthErrorCode: FIRAuthErrorCode) {
switch errCode {
case .ErrorCodeInvalidEmail:
self = .invalidEmail
case .ErrorCodeUserDisabled:
self = .userDisabled
case .ErrorCodeWrongPassword:
self = .wrongPassword
case .ErrorCodeUserNotFound:
self = .userNotFound
case .ErrorCodeNetworkError:
self = .networkError
default:
self = .unknownError
}
}
}
You need to inform the user appropriately about the different errors/incorrect states of course, not only print.
This code should probably me moved to a ViewModel, and if your are not using MVVM, I highly recommend it :).
if let email = emailTextfield.text, let password = passwordTextfield.text {
Auth.auth().signIn(withEmail: email, password: password) { authResult, error in
if let e = error{
print(e.localizedDescription)
}
else {
//Do whatever you want to do after successful login
}
}
}
You can try this also:
if( !Auth.auth().currentUser!.isEmailVerified) {
// do something
}

The method return the varible without modifying it

i have a problem with my code in Swift
the method verifyInput should return false when there's an error
but it always return true no matter what + when there's an error it print "error" but its just return true
please help
#IBAction func register(_ sender: UIButton) {
let check = verifyInput(email :email.text! ,password: password.text!)
if(check==true){
self.performSegue(withIdentifier: "goToAmazon", sender: nil)
} else if(check==false) {
self.message.text = "Sorry! there's an error"
}
}
func verifyInput(email: String, password: String) -> Bool {
var check = true
Auth.auth().createUser(withEmail: email, password: password) { (user, error) in
if error != nil {
print("error")
check = false
} else if(error==nil){
check = true
print("registered!")
}
}
return check
}
The problem is that verifyInput is being called synchronously from register but within it is an asynchronous call to Auth.auth().createUser with a completion block.
The check result is being returned before the asynchronous call ever completes. You need to change your method to be asynchronous as well.
Something vaguely like this is what you want:
#IBAction func register(_ sender: UIButton) {
if let email = email.text, let password = password.text {
verifyInput(email: email, password: password) { (check) in
DispatchQueue.main.async {
// only run UI code on the main thread
if(check){
self.performSegue(withIdentifier: "goToAmazon", sender: nil)
} else {
self.message.text = "Sorry! there's an error"
}
}
}
}
}
func verifyInput(email: String, password: String, escaping completion:#escaping (Bool)->Void) {
Auth.auth().createUser(withEmail: email, password: password) { (user, error) in
if error != nil {
print("error")
completion(false)
} else if(error==nil){
print("registered!")
completion(true)
}
}
}

Firebase automatic login on iOS

Is there a better way to make a user automatically login to the app as long as they have logged in in the past; other than saving their login details directly to the storage which may be insecure?
Thanks
PS: I'm using Firebase and Swift for iOS
EDIT: Here's the code
import UIKit
import Firebase
class loginVC: UIViewController {
#IBOutlet weak var emailTextField: UITextField!
#IBOutlet weak var passwordTextField: UITextField!
#IBOutlet weak var errorLabel: UILabel!
override func viewDidAppear(animated: Bool) {
automaticLogin()
}
#IBAction func loginTapped(sender: AnyObject) {
errorLabel.text = ""
FIRAuth.auth()?.createUserWithEmail(emailTextField.text!, password: passwordTextField.text!, completion: {
user, error in
if error != nil {
FIRAuth.auth()?.signInWithEmail(self.emailTextField.text!, password: self.passwordTextField.text!, completion: { (user, error) in
if error == nil {
self.login()
} else if error != nil {
self.errorLabel.text = ("Invalid email address or password")
print(error)
}
})
} else {
print("user created")
self.login()
print(user?.displayName)
}
})
}
#IBAction func forgotPasswordTapped(sender: AnyObject) {
let email = emailTextField.text
errorLabel.text = ""
FIRAuth.auth()?.sendPasswordResetWithEmail(email!) { error in
if error != nil {
self.errorLabel.text = ("Invalid email address")
print(error)
} else {
self.errorLabel.text = ("Password reset email successfully sent")
print("password reset email sent")
}
}
}
func login() {
FIRAuth.auth()?.signInWithEmail(emailTextField.text!, password: passwordTextField.text!, completion: {
user, error in
if error != nil {
self.errorLabel.text = ("Invalid email address or password")
print(error)
} else {
print("login successful")
self.checkIfUserIsNew()
}
})
}
func checkIfUserIsNew() {
if FIRAuth.auth()?.currentUser?.displayName != nil || FIRAuth.auth()?.currentUser?.displayName == "" {
self.performSegueWithIdentifier("showChatVC", sender: self)
} else {
self.performSegueWithIdentifier("showOptionsVC", sender: self)
}
}
func automaticLogin() {
FIRAuth.auth()?.addAuthStateDidChangeListener { auth, user in
if user == user {
print("still signed in")
} else {
print("not signed in")
}
}
}
I also used this to logout the user:
#IBAction func logOutTapped(sender: AnyObject) {
try! FIRAuth.auth()?.signOut()
performSegueWithIdentifier("showLoginVC", sender: self)
print(FIRAuth.auth()?.currentUser?.displayName)
}
You might want to use NSUserDefaults. To do this in your login() function you might want to save your user's data once they signed in or create an account. Inside your login function you can start by creating new variable to grab your user's email / password.
let userDefaults = UserDefaults.standard
userDefaults.setValue(self.emailField.text!, forKey: "email")
userDefaults.setValue(self.passField.text!, forKey: "password")
Next, you also need to implement a new function called viewWillAppear, which will be called before the view controller. And you might also put some arguments inside your login function.
override func viewWillAppear(_ animated: Bool) {
let userDefaults = UserDefaults.standard
if userDefaults.string(forKey: "email") != nil {
let email = userDefaults.string(forKey: "email")
let password = userDefaults.string(forKey: "password")
login(email: email!, password: password!)
}
}
the if statement will check wether your user already registered their email account or not yet. Lastly you need to update your login function to have two parameters, to pass in the email and password of the user.

Resources