1

I am trying to implement a function to delete current user's account on iOS. Account deletion works properly but the problem is that I cannot delete the account's data from Database and Storage when deleting an account.

"currentUser.delete" deletes the account but I think there is no authentication to delete its data from Database and Storage. Permission denied message shows up in the log. After running this function, I get to see the account is gone in Firebase Console Authentication page but data from Database and Storage persists.

Is this the correct way to delete an account?

I tried to delete data from Database and Storage before deleting the account. However, Firebase asks for re-authentication if session is more than 5 minutes old. Re-login shows empty data to the user before performing account deletion again so this is misleading and very confusing.

Please let me know how to remove data when deleting an account.

private func deleteAccount() {
  guard let currentUser = Auth.auth().currentUser else {
    return print("user not logged in")
  }

  currentUser.delete { error in
    if error == nil {
      // 1. Delete currentUser's data from Database. Permission denied
      // 2. Delete currentUser's data from Storage. Permission denied
      // present login screen (welcome page)
      self.presentLoginScreen()
    } else {
      guard let errorCode = AuthErrorCode(rawValue: error!._code) else { return }

      if errorCode == AuthErrorCode.requiresRecentLogin {
        self.showMessage("Please re-authenticate to delete your account.", type: .error)

        do {
          try Auth.auth().signOut()
          self.presentLoginScreen()
        } catch {
          print("There was a problem logging out")
        }
      }
    }
  }
}
Bijoy Thangaraj
  • 5,434
  • 4
  • 43
  • 70
  • Removing the data prior to account deletion is the only way to make this happen from the client. But I have no idea what you mean when you say "I tried to delete data from Database and Storage before deleting the account. However, Firebase asks for re-authentication if session is more than 5 minutes old." I don't understand what the problem is here. If the user account is deleted, that's pretty much the end of the line, right? – Doug Stevenson Jun 04 '18 at 05:39
  • Hey Doug, if a user's session is more than 5 minutes old, he goes back to welcome page. But then this user will see empty data and has to go to settings page to perform account deletion again. I think this is pretty confusing. 1. User goes to settings page to delete account. 2. He is asked to re-authenticate and automatically goes back to welcome page. 3. He logs in again but the data is all gone and the account is still alive. 4. He has to go to settings page to delete account again. Isn't this weird? – Youngsun Paik Jun 04 '18 at 05:43
  • You're deleting the account. I would expect everything to reset in that case. Session age has nothing to do with anything. Is there something going on here that you're not sharing? – Doug Stevenson Jun 04 '18 at 06:01
  • Dont you need to identify user related data with UID? So you just make a function to delete stuff containing/related to that specific UID. – Karlo A. López Jun 04 '18 at 06:16
  • Session age, I believe, does matter. If it is more than 5 minutes old, I get an error, which is handled in else statement. So this error prevents deleting account if the session is old. – Youngsun Paik Jun 04 '18 at 06:18
  • Karlo, I do have lines with UID like this: DatabaseReferenceHelper.usersRef.child(currentUser.uid).removeValue(completionBlock: { (error, ref) in if error != nil { print("Failed to delete item data:", error?.localizedDescription as Any) return } }) I just took them out for visibility. I think what Doug said is correct about deleting data should be performed before deleting account. – Youngsun Paik Jun 04 '18 at 06:19
  • Hey Doug, I am going to add password text field in this view so user always has to re-authenticate before deleting account. Thanks! – Youngsun Paik Jun 04 '18 at 06:33

2 Answers2

1

Swift 5 | Firebase 8.11.0

To solve the problems that you've mentioned (delete the data before deleting the actual user and potentially get the AuthErrorCode.requiresRecentLogin error), you may use DispatchGroup and check the lastSignInDate, like this (just call deleteUserProcess()):

let deleteDataGroup = DispatchGroup()

func deleteUserProcess() {
    guard let currentUser = Auth.auth().currentUser else { return }
    deleteUserData(user: currentUser)
    // Call deleteUser only when all data has been deleted
    deleteDataGroup.notify(queue: .main) {
        self.deleteUser(user: currentUser)
    }
}
/// Remove data from Database & Storage
func deleteUserData(user currentUser: User) {
    // Check if `currentUser.delete()` won't require re-authentication
    if let lastSignInDate = currentUser.metadata.lastSignInDate,
        lastSignInDate.minutes(from: Date()) >= -5 {
        deleteDataGroup.enter()
        Database.database().reference().child(userId).removeValue { error, _ in
            if let error = error { print(error) }
            self.deleteDataGroup.leave()
        }
        // Delete folders from Storage isn't possible,
        // so list and run over all files to delete each one independently
        deleteDataGroup.enter()
        Storage.storage().reference().child(userId).listAll { list, error in
            if let error = error { print(error) }
            list.items.forEach({ file in
                self.deleteDataGroup.enter()
                file.delete { error in
                    if let error = error { print(error) }
                    self.deleteDataGroup.leave()
                }
            })
            deleteDataGroup.leave()
        }
    }
}

/// Delete user
func deleteUser(user currentUser: User) {
    currentUser.delete { error in
        if let error = error {
            if AuthErrorCode(rawValue: error._code) == .requiresRecentLogin {
                reauthenticate()
            } else {
                // Another error occurred
            }
            return
        }

        // Logout properly
        try? Auth.auth().signOut()
        GIDSignIn.sharedInstance.signOut()
        LoginManager().logOut()

        // The user has been deleted successfully
        // TODO: Redirect to the login UI
    }
}

func reauthenticate() {
    // TODO: Display some UI to get credential from the user
    let credential = ... // Complete from https://stackoverflow.com/a/38253448/8157190
    Auth.auth().currentUser?.reauthenticate(with: credential) { _, error in
        if let error = error {
            print(error)
            return
        }

        // Reload user (to update metadata.lastSignInDate)
        Auth.auth().currentUser?.reload { error in
            if let error = error {
                print(error)
                return
            }
            // TODO: Dismiss UI
            // Call `deleteUserProcess()` again, this time it will delete the user
            deleteUserProcess()
        }
    }
}

The minuets function can be added in an extension to Date (thanks to Leo Dabus):

extension Date {
    /// Returns the amount of minutes from another date
    func minutes(from date: Date) -> Int {
        return Calendar.current.dateComponents([.minute], from: date, to: self).minute ?? 0
    }
}
Ido
  • 473
  • 4
  • 16
0

you can first make your specific user deleted and and its value through its UID then you can deleted user and take him to root view controller or login screen after deleting it.

// removing user data from firebase and its specific user id 
         let user = Auth.auth().currentUser
        user?.delete { error in
          if let error = error {
            // An error happened.
            print(error.localizedDescription)
          } else {
            Database.database().reference().child("users").child(user?.uid ?? "").removeValue()
            self.navigationController?.popToRootViewController(animated: true)
            // Account deleted and logout user
//            do {
//                try Auth.auth().signOut()
                // take you to root
//                self.navigationController?.popToRootViewController(animated: true)


        }
Akbar
  • 15
  • 10