iOS SWIFT: Unable to delete user from Firebase Database - ios

I want to delete my current user from Firebase. The authenticated user gets deleted however, I am unable to delete the data for that user in the database. What am i doing wrong?
This is my delete user method....
FIRAuth.auth()?.signIn(withEmail: (emailTextField?.text)! , password: (passwordTextField?.text)!, completion: { (user, error) in
if error == nil {
print("User Authenticate!!!")
let user = FIRAuth.auth()?.currentUser
user?.delete(completion: { (error) in
if error != nil {
print("Error unable to delete user")
} else {
DataService.ds.deleteCurrentFirebaseDBUser()
KeychainWrapper.standard.removeObject(forKey: KEY_UID)
self.performSegue(withIdentifier: "goToLogin", sender: nil)
}
})
} else {
//Password was wrong, unable to authenicate user. Data is not updated
print("!!!ALERT!!! Unable to authenticate user")
let alert = UIAlertController(title: "Incorrect Password", message: "Please re-enter your password", preferredStyle: UIAlertControllerStyle.alert)
alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.default, handler: nil))
self.present(alert, animated: true, completion: nil)
}
})
Firebase Rules:
{
"rules": {
"users": {
"$uid": {
".read": "$uid === auth.uid",
".write": "$uid === auth.uid"
}
}
}
}
Database:
App
-> users
->
4erkjkl543jfe46
->name
->email
ERRORS:
2017-01-21 21:33:10.321704 APP[11582:4102711] [FirebaseDatabase] setValue: or removeValue: at /users/4erkjkl543jfe46 failed: permission_denied
Optional(Error Domain=com.firebase Code=1 "Permission denied" UserInfo={NSLocalizedDescription=Permission denied})

I'm having the same issue. You are not able to make use of your function deleteCurrentFirebaseDBUser() because the Firebase delete function (if successful) removes the user auth object.
As a result user is not authenticated anymore at the time you want to delete user's data in database with deleteCurrentFirebaseDBUser().
Currently I delete user's data in database before Firebase delete function which is not the ideal solution.

We can delete user from both side authentication and database.But before that we need to reauthenticate user first then we get latest token to delete the user.
Here is the pretty code:
let user = Auth.auth().currentUser
user?.reauthenticate(with:credential) { error in
if let error = error {
// An error happened.
showAlertWithErrorMessage(message: error.localizedDescription)
} else {
// User re-authenticated.
user?.delete { error in
if let error = error {
// An error happened.
showAlertWithErrorMessage(message: error.localizedDescription)
} else {
// Account deleted.
let userID = HelperFunction.helper.FetchFromUserDefault(name: kUID)
Database.database().reference(fromURL: kFirebaseLink).child(kUser).child(userID).removeValue()
try! Auth.auth().signOut()
showAlertWithErrorMessage(message: "Your account deleted successfully...")
return
}
}
}
}
100% working in my project and well tested

for just to delete a child from Firebase use "removeValue()"
var db: DatabaseReference!
override func viewDidLoad() {
super.viewDidLoad()
db = Database.database().reference()
deleteByID()
}
func deleteByID(){
db.child("YOURID").removeValue()
}

Swift 5 | Firebase 8.11.0
As #SvshX said, deleting the user data before deleting the actual user is the only available solution.
The problem with this method is that deleting the user might give an error like AuthErrorCode.requiresRecentLogin, then the data will be deleted but the user will not.
This error is given when the last authentication of the user was more than 5 minuets ago (from Firebase Docs)
So, fixing both of the issues can be achieved by using DispatchGroup and checking the lastSignInDate.
This is my final solution (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
}
}

Related

How to update Email Address in Firebase Authentication in Swift 4

i just want to update authenticate email address of current user. i have tried lot's of solution like updateEmail method of firebase but it not work !! if any one know then please tell me how can i achieved this Thanks in advance !!
#IBAction func btnResetEmailClick(_ sender: UIButton) {
let auth = Auth.auth()
guard let email = self.txtEmailAddress.text ?? auth.currentUser?.email else { return }
// email that i have to update with current user email
auth.currentUser?.updateEmail(to: (auth.currentUser?.email)!, completion: { (error) in
if error == nil{
}else{
}
})
}
To change the email address the user has to be logged in recently i would suggest doing this:
var credential: AuthCredential
#IBAction func changeEmail() {
if let user = Auth.auth().currentUser {
// re authenticate the user
user.reauthenticate(with: credential) { error in
if let error = error {
// An error happened.
} else {
// User re-authenticated.
user.updateEmail(to: "email") { (error) in
// email updated
}
}
}
}
}
This is effective method to solve it.
let user = Auth.auth().currentUser
user?.updateEmail(to: "email") { error in
if error != nil {
// An error happened
} else {
// Email updated.
}
}

How to change firebase email without reauthentication

I implemented a button in my app that allows the user to change their email using Firebase.
#IBAction func resetEmail(_ sender: Any) {
let alertController = UIAlertController(title: "Change Email", message: "", preferredStyle: .alert)
alertController.addTextField { (textField : UITextField!) -> Void in
textField.placeholder = "Enter New Email Address"
let saveAction = UIAlertAction(title: "Save", style: .default, handler: { (action : UIAlertAction!) -> Void in
//Reset Email
let currentUser = Auth.auth().currentUser
if Auth.auth().currentUser != nil{
currentUser?.updateEmail(to: textField.text!) { error in
if let error = error {
print(error)
} else {
print("CHANGED")
let user = Auth.auth().currentUser
let name = user?.displayName!
let ref = Database.database().reference().child("main").child("users_sen").child(name!).child("email")
ref.setValue(textField.text!)
}
}
}
})
alertController.addAction(saveAction)
}
self.present(alertController, animated: true, completion: {
alertController.view.superview?.isUserInteractionEnabled = true
alertController.view.superview?.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.alertClose(gesture:))))
})
}
However, when I run it and I try to change the email it gives me this error:
UserInfo={NSLocalizedDescription=This operation is sensitive and requires
recent authentication. Log in again before retrying this request.
and tells me to re-sign in order to change the email. How do I avoid this? How do I change the email without re-signing in?
This is how I change the password:
// Password updated.
let currentUser = Auth.auth().currentUser
currentUser?.updatePassword(to: textField.text!) { error in
if let error = error {
} else {
// Password updated.
print("success")
}
}
let userEmail = Auth.auth().currentUser?.email
self.currentPassword = textField.text!
let credential = EmailAuthProvider.credential(withEmail: userEmail!, password: textField.text!)
currentUser?.reauthenticate(with: credential) { error in
if let error = error {
// An error happened.
} else {
// User re-authenticated.
}
}
Base on Firebase's documentation, you need to re-authenticate the user when performing this type of action.
Re-authenticate a user Some security-sensitive actions—such as
deleting an account, setting a primary email address, and changing a
password—require that the user has recently signed in. If you perform
one of these actions, and the user signed in too long ago, the action
fails with an error. When this happens, re-authenticate the user by
getting new sign-in credentials from the user and passing the
credentials to reauthenticateWithCredential.
let user = Auth.auth().currentUser
let credential = EmailAuthProvider.credential(withEmail: "email", password: "password")
user?.reauthenticate(with: credential)
{ error in
if let error = error {
// An error happened.
} else {
// User re-authenticated.
user?.updateEmail(to: "newemail")
{ error in
}
}
}
To change user email without re-authentication you can also leverage Cloud Functions. An example course of action could be:
Create a function that accepts user access token and new email address as parameters
In the function, verify access token and get the user ID from it
In the function, call
admin.auth().updateUser(userId, { email: newEmail })
Call the new function from the client
Note: This solution is less secure because the user intent is not verified by additional authentication. Therefore anyone getting hold of the user's device could change their email address.
If you use email and password to authenticate a user you should to do something like this.
You have to re-authenticate user using credential
Re-authenticate user
Update email
Before don't forget to get current user in your class and import Firebase like this :
...
import Firebase
class Blabla {
...
var currentUser: User? {
return Auth.auth().currentUser
}
Then :
func updateUserEmail(newEmail: String, password: String) {
// 1. Get the credential
guard let currentEmail = currentUser?.email else {return}
var credential = EmailAuthProvider.credential(withEmail: currentEmail, password: password)
You can't get directly password, so you must ask to the user his password by a textfield or other.
// 2. Re-authenticate the user
//(To change mail or password, the user must to be authentificate a short time ago !!!)
self.currentUser?.reauthenticate(with: credential, completion: { (result, error) in
if error != nil {
print("ERROR: ", error?.localizedDescription)
return
}
//3. Update email
self.currentUser?.updateEmail(to: newEmail, completion: { (error) in
if error != nil {
print("ERROR: ", error?.localizedDescription)
}else {
//Do something, for example present an alert of confirmation..
}
})
})
All of the code in the same function from the step 1.

Firebase delete account along with Database and Storage on iOS

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")
}
}
}
}
}
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
}
}
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)
}

Link multiple auth providers on firebase

Currently I allow users to "Sign In with Facebook":
#objc func handleFBLogin() {
FBSDKLoginManager().logIn(withReadPermissions: ["email", "public_profile"], from: self) { (result, error) in
if error != nil {
print(error as Any)
return
}
self.handleFBAccessToken()
}
}
func handleFBAccessToken() {
let accessToken = FBSDKAccessToken.current()
guard let accessTokenString = accessToken?.tokenString else { return }
let credentials = FacebookAuthProvider.credential(withAccessToken: accessTokenString)
Auth.auth().signIn(with: credentials) { (user, error) in
if error != nil {
// I assume I handle the errors here
print(error as Any)
return
}
// successfully logged in user
self.instantiateTabVC()
}
FBSDKGraphRequest(graphPath: "/me", parameters: ["fields": "id, name, email"]).start { (connection, result, error) in
if error != nil {
print(error as Any)
}
}
}
I'd like to add it so that if they've already made an account via email/password, the two accounts will be automatically linked/merged (or vice versa). In "I assume I handle the errors here", I added
let providers = Auth.auth().fetchProviders(forEmail: AuthErrorUserInfoEmailKey)
// sign in with existing account
// call linkWithCredential:completion:
to fetch the email that already exists. I am basing this off of this and this (both similar questions). I understand that the premise is to
use fetchProvidersForEmail with that email which will lookup the provider IDs associated with that email. You then sign in the user with one of those providers. After you finish sign-in with the existing account, you call linkWithCredential:completion: with the original credential that caused the error to occur
However, I am new to Swift and extremely confused about how to go about that. Any sample code would be extremely beneficial. I've also tried reading the documentation but that hasn't helped either (as I probably have not learned to properly understand the documentation)
I hope that I am moving in the right direction to solve this problem, however, if you have any other suggestions, I'd be open to that. I appreciate any help
Try this Approach this will doesn't create a new user if exists.
func facebook() {
let loginManager: FBSDKLoginManager = FBSDKLoginManager()
loginManager.logIn(withReadPermissions: ["email"], from: self, handler: { result, error in
if let error = error {
self.showMessagePrompt(error.localizedDescription)
} else if result!.isCancelled {
print("FBLogin cancelled")
} else {
// [START headless_facebook_auth]
let credential = FacebookAuthProvider.credential(withAccessToken: FBSDKAccessToken.current().tokenString)
// [END headless_facebook_auth]
self.firebaseLogin(credential)
}
})
}
func firebaseLogin(_ credential: AuthCredential) {
if let user = Auth.auth().currentUser {
// [START link_credential]
user.link(with: credential) { _, error in
// [START_EXCLUDE]
if let error = error {
self.showMessagePrompt(error.localizedDescription)
return
}
// [END_EXCLUDE]
}
// [END link_credential]
} else {
// [START signin_credential]
Auth.auth().signIn(with: credential) { _, error in
// [START_EXCLUDE silent]
self.hideSpinner {
// [END_EXCLUDE]
if let error = error {
// [START_EXCLUDE]
self.showMessagePrompt(error.localizedDescription)
// [END_EXCLUDE]
return
}
// User is signed in
// [START_EXCLUDE]
// Merge prevUser and currentUser accounts and data
// ...
// [END_EXCLUDE]
}
}
// [END signin_credential]
}
}

User incorrectly getting logged in with invalid data, after having logged out previously

I have a login screen that ensures that there is valid data in the input fields before attempting to login. Or so I thought.
The problem is when we come back from another screen that "logouts" the user, if I submit with an invalid username password combo after returning to this page, I see the error dialog as expected, but after dismissing it I am then taken to the next view controller as if I logged in.
Any help please?
#IBAction func btnSubmit(sender: UIButton) {
if txtUsername.text == "" || txtPassword.text == "" {
//they're missing a username or password
displayAlert("Missing Field(s)", message: "Please enter both a username and password")
}else {
//we check if they're in signup/login mode
if Switch.on {
//user is in signup mode
if txtPassword.text != txtConfirmPassword.text {
//the password fields do not match
displayAlert("Mismatched Passwords", message: "Please enter matching passwords")
}else {
//the password fields do match, and the user can register with this username/email and password
var user = PFUser()
user.username = txtUsername.text
user.password = txtPassword.text
// other fields can be set just like with PFObject
user.signUpInBackgroundWithBlock {
(succeeded: Bool, error: NSError?) -> Void in
if let error = error {
let errorString = error.userInfo?["error"] as! String
// Show the errorString somewhere and let the user try again.
self.displayAlert("Signup Error", message: errorString)
} else {
// Hooray! Let them use the app now.
self.performSegueWithIdentifier("register", sender: self)
}
} }
}else {
//user is in login mode and we can submit credentials
PFUser.logInWithUsernameInBackground(txtUsername.text, password:txtPassword.text) {
(user: PFUser?, error: NSError?) -> Void in
if let error = error {
let errorString = error.userInfo?["error"] as! String
// Show the errorString somewhere and let the user try again.
self.displayAlert("Login Error", message: errorString)
} else {
if PFUser.currentUser()!.username != nil {
// Do stuff after successful login.
self.performSegueWithIdentifier("login", sender: self)
}
}
}
}
}
}
Here's my logout call from the other page
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "logout" {
PFUser.logOut()
}
}
I believe that the issue is that you're not uses the succeeded Bool that is returned in the completion handler. When I log users in with Parse I use that primarily to see if log in was successful or not, if it is not successful I will then check what the error message was. This should stop you from allowing the user to continue into the app when they have not been logged in correctly.
user.signUpInBackgroundWithBlock {
(succeeded: Bool, error: NSError?) -> Void in
if error != nil {
let errorString = error!.userInfo?["error"] as! String
// Show the errorString somewhere and let the user try again.
self.displayAlert("Signup Error", message: errorString)
} else {
if succeeded {
// Hooray! Let them use the app now.
self.performSegueWithIdentifier("register", sender: self)
} else {
//Something went wrong
}
}
}

Resources