How to Sign out and Delete Users Register By FirebaseUI Swift 5 - swift

I want to give my app the ability to Log out/delete the user and when I type this code
#IBAction func deleteTheAccountButtonHasBeenTapped(_ sender: Any) {
let user = Auth.auth().currentUser
var credential: AuthCredential
user?.reauthenticateAndRetrieveData(with: credential, completion: {(authResult, error) in
if let error = error {
// An error happened.
print(error)
}else{
//user re-authenticated
user?.delete { error in
if let error = error {
// An error happened.
print(error)
} else {
// Account deleted.
let vc = UIStoryboard.init(name: "Main", bundle: Bundle.main).instantiateViewController(withIdentifier: "LoginVC") as! LoginVC
self.present(vc, animated:true, completion:nil)
}
}
}
})
}
I got this error:
Variable 'credential' used before being initialized
any one can help me?

The error message is pretty explicit: you're using credential before you initialized it.
In order to delete the user, you need to first reauthenticate them, as shown in the documentation on reauthenticating the user. Your version doesn't implement this comment from that code:
// Prompt the user to re-provide their sign-in credentials
How to reauthenticate the user depends on the provider. For example for email/password, you'd get them with:
credential = EmailAuthProvider.credential(withEmail: email, password: password)
A handy place to find similar snippets for other providers is in the documentation on linking accounts.

Related

How to allow user to sign in with both email +password and with mobile number+password with Firebase IOS

I am trying to make the following things:
Sign in with number and password. (Connect email/number)
Use one textfield for email/ number when the user can type one of the options and firebase will login in to that user account
I looked up at Firebase docs but there was not no mention about having Phone Number / Password auth.
For example, I signed up: email: SOME_MAIl password: SOME_PASSWORD number: SOME_NUMBER I want to make something like this
If numberAuth {
Auth.auth.signIn(withNumber: number, password: password )
} else {
Auth.auth.signIn(email: email, password: password ) }
I found out that I have to customize firebase auth but I don't have an idea how to custom that. How that should be done?
P.S.: My idea is similar to this question Firebase Authentication connect Email with Phone
In the image below the idea is illustrated
Here is my code:
func signIn() {
if let password = passwordValue.text, let email = emailValue.text {
Auth.auth().signIn(withEmail: email, password: password ) { [weak self] authResult, error in
if let e = error {
print(e)
let alert = Service.createAlertController(title: "Error", message: e.localizedDescription)
self?.present(alert, animated: true,completion: nil)
} else {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let vc = storyboard.instantiateViewController(identifier: "main")
vc.modalPresentationStyle = .overFullScreen
self?.present(vc, animated: true)
}
}
}
}

Swift Firebase Twitter Login: Unable to get credential

I am implementing Firebase Authentication with Twitter login and I followed the docs but wasn't able to get any response from getCredentialWith. Other SO post on this topic is outdated as most still uses TwitterKit, which has already stopped being supported.
My implementation so far:
//At viewDidLoad()
navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Login", style: .plain, target: self, action: #selector(loginWithTwitter))
#objc func loginWithTwitter() {
print("Login button tapped")
let provider = OAuthProvider(providerID: "twitter.com")
provider.getCredentialWith(nil) { credential, error in
print(2) // <== Not printed
if error != nil {
// Handle error.
print("Err", error)
}
if let credential = credential {
Auth.auth().signIn(with: credential) { authResult, error in
print(3) // <== Not printed
if error != nil {
// Handle error.
print("Err auth", error)
}
if let authResult = authResult {
print(authResult.credential)
}
}
}
}
Oddly, the above code, as copy and pasted from the docs, doesn't seem to return to print 2.
I have ensured that I have done the following:
Added URL schemes to the Info tab
Enabled Twitter sign in on Firebase Authentication
Added the necessary API key and secret key on Firebase Auth
Added callback URL to Twitter developer dashboard
What am I missing?
I managed to solve it by:
Adding a website URL to the authentication settings in Twitter Developer portal
Moving let provider = OAuthProvider(providerID: "twitter.com") to a global variable

How to check if email already exist before creating an account (Swift)

I know different variations of this question have been asked. However I seem to keep running into the same issue every time.
I want to check if an email already exist before the user pushes onto the next view. I will enter an email that exist in the database and the performSegue func is always called and pushes the user as if that email does not exist.
The only way I can check officially is when the user reaches the final sign up VC and the Auth.auth().createUser(withEmail: email as! String, password: password as! String ) { (user, error) in code will check for all errors.
However for good user experience I would hate for the user to have to click back three times to change the email address. Here is the code I have for the enter email view controller.
// Check if email is already taken
Auth.auth().fetchSignInMethods(forEmail: emailTextField.text!, completion: { (forEmail, error) in
// stop activity indicator
self.nextButton.setTitle("Continue", for: .normal)
self.activityIndicator.stopAnimating()
if let error = error {
print("Email Error: \(error.localizedDescription)")
print(error._code)
self.handleError(error)
return
} else {
print("Email is good")
self.performSegue(withIdentifier: "goToCreateUsernameVC", sender: self)
}
})
First off am I even entering the create property in the forEmail section? I added emailTextField.text because its the only way I know how even get the email the user typed. Does anyone know a better way I can do this?
How I create user accounts
This is an example of what I use. When a user provides credentials, FirebaseAuth checks if these credentials can be used to make a user account. The function returns two values, a boolean indicating whether the creation was successful, and an optional error, which is returned when the creation is unsuccessful. If the boolean returns true, we simply push to the next view controller. Otherwise, we present the error.
func createUserAcct(completion: #escaping (Bool, Error?) -> Void) {
//Try to create an account with the given credentials
Auth.auth().createUser(withEmail: emailTextField.text!, password: passwordConfirmTextField.text!) { (user, error) in
if error == nil {
//If the account is created without an error, then we will make a ProfileChangeRequest, i.e. update the user's photo and display name.
if let firebaseUser = Auth.auth().currentUser {
let changeRequest = firebaseUser.createProfileChangeRequest()
//If you have a URL for FirebaseStorage where the user has uploaded a profile picture, you'll pass the url here
changeRequest.photoURL = URL(string: "nil")
changeRequest.displayName = self.nameTextField.text!
changeRequest.commitChanges { error in
if let error = error {
// An error happened.
completion(false, error)
} else {
//If the change is committed successfully, then I create an object from the credentials. I store this object both on the FirebaseDatabase (so it is accessible by other users) and in my user defaults (so that the user doesn't have to remotely grab their own info
//Create the object
let userData = ["email" : self.emailTextField.text!,"name": self.nameTextField.text!] as [String : Any]
//Store the object in FirebaseDatabase
Database.database().reference().child("Users").child(firebaseUser.uid).updateChildvalues(userData)
//Store the object as data in my user defaults
let data = NSKeyedArchiver.archivedData(withRootObject: userData)
UserDefaults.standard.set(data, forKey: "UserData")
UserDefaults.standard.set([Data](), forKey: "UserPhotos")
completion(true, nil)
}
}
}
} else {
// An error happened.
completion(false, error)
}
}
}
Here is an example of how I would use it. We can use the success boolean returned to determine if we should push to the next view controller, or present an error alert to the user.
createUserAcct { success, error in
//Handle the success
if success {
//Instantiate nextViewController
let storyboard = UIStoryboard(name: "Main", bundle: .main)
let nextVC = storyboard.instantiateViewController(withIdentifier: "NextVC") as! NextViewController
//Push typeSelectVC
self.navigationController!.pushViewController(viewController: nextVC, animated: true, completion: {
//We are no longer doing asynchronous work, so we hide our activity indicator
self.activityIndicator.isHidden = true
self.activityIndicator.stopAnimating()
})
} else {
//We now handle the error
//We are no longer doing asynchronous work, so we hide our activity indicator
self.activityIndicator.isHidden = true
self.activityIndicator.stopAnimating()
//Create a UIAlertController with the error received as the message (ex. "A user with this email already exists.")
let alertController = UIAlertController(title: "Error", message: error!.localizedDescription, style: .alert)
let ok = UIAlertAction(title: "OK", style: .cancel, action: nil)
//Present the UIAlertController
alertController.addAction(ok)
self.present(alertController, animated: true, completion: nil)
}
}
Let me know if this all makes sense, I know there is a lot to it. I'm just considering things you'll maybe find you need done anyways that you may not be aware of (like making change requests, or storing a data object on FirebaseDatabase).
Now for checking if the email is already taken:
Remember when I said that I post a user object to FirebaseDatabase upon account creation? Well we can query for the given email to see if it already exists. If it doesn't we continue with the flow as normal, without having actually created the account. Otherwise, we simply tell the user to pick another email address.
Pushing a user object to your database (taken from the above code):
if let firebaseUser = Auth.auth().currentUser {
//Create the object
let userData = ["email" : self.emailTextField.text!,"name": self.nameTextField.text!] as [String : Any]
//Store the object in FirebaseDatabase
Database.database().reference().child("Users").child(firebaseUser.uid).updateChildvalues(userData)
}
And now querying to see if somebody already has that email:
func checkIfEmailExists(email: String, completion: #escaping (Bool) -> Void ) {
Database.database().reference().child("Users").queryOrdered(byChild: "email").queryEqual(toValue: email).observeSingleEvent(of: .value, with: {(snapshot: DataSnapshot) in
if let result = snapshot.value as? [String:[String:Any]] {
completion(true)
} else {
completion(false)
}
}
}
Then we can call this like so:
checkIfEmailExists(email: emailTextField.text!, completion: {(exists) in
if exists {
//Present error that the email is already used
} else {
//Segue to next view controller
}
})

Signing Out of Firebase in Swift

I am attempting to sign out of the Firebase API, but I can't seem to figure out how to handle any errors that may occur.
The Firebase pod provides a method for signing out:
FIRAuth.auth()?.signOut()
It is marked with throws, so I have wrapped it in a do/try/catch block in a method to test the signing out process:
do {
try FIRAuth.auth()?.signOut()
} catch (let error) {
print((error as NSError).code)
}
I see that the signOut method is marked with throws in the Firebase pod, but I don't see how it can handle any errors asynchronously. I have tried entering Airplane Mode, which triggers a network error in my code everywhere else that a network request takes place, but with the signOut method, that error isn't caught because I have no completion handler to execute from. All of the other authentication methods from the Firebase pods have a completion handler, in which I am able to handle errors.
Here is the documentation for the signOut method from the Firebase pod:
/** #fn signOut:
#brief Signs out the current user.
#param error Optionally; if an error occurs, upon return contains an NSError object that
describes the problem; is nil otherwise.
#return #YES when the sign out request was successful. #NO otherwise.
#remarks Possible error codes:
- #c FIRAuthErrorCodeKeychainError Indicates an error occurred when accessing the keychain.
The #c NSLocalizedFailureReasonErrorKey field in the #c NSError.userInfo dictionary
will contain more information about the error encountered.
*/
open func signOut() throws
Do you have any suggestions for an appropriate way to handle the signing out of a user when I don't have a completion handler that allows me to check for an error?
You can catch the error like this
do
{
try Auth.auth().signOut()
}
catch let error as NSError
{
print(error.localizedDescription)
}
Edited from Milli's answer to add sending user back to initial page of the app.
// log out
func logout(){
do
{
try Auth.auth().signOut()
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let IntroVC = storyboard.instantiateViewController(withIdentifier: "IntroVC") as! introVC
let appDelegate = UIApplication.shared.delegate as! AppDelegate
appDelegate.window?.rootViewController = IntroVC
}
catch let error as NSError
{
print(error.localizedDescription)
}
}
An error is highly unlikely to occur but it's never good to assume anything. By the sound of the documentation, it wipes out your keychain which is the only way you'd be able to log back into your firebase application. From trying logging out of my own firebase app I was surprise that 0 errors occured. Here is the original code.
#IBAction func logOutTapped(_ sender: Any) {
let firebaseAuth = FIRAuth.auth()
do {
try firebaseAuth?.signOut()
} catch let signOutError as NSError {
print ("Error signing out: %#", signOutError)
}
if Utility.hasFacebook {
let login = FBSDKLoginManager()
login.logOut()
}
if Utility.hasTwitter {
Twitter.sharedInstance().sessionStore.logOutUserID((Twitter.sharedInstance().sessionStore.session()?.userID)!)
}
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let initialViewController = storyboard.instantiateViewController(withIdentifier: "LoginVC")
self.present(initialViewController, animated: false)
}
Anyways if you really want a completion handler then here's something I tossed up quickly
func logOut(completion:#escaping(_ errorOccured: Bool) -> Void) {
let firebaseAuth = FIRAuth.auth()
do {
try firebaseAuth?.signOut()
} catch let signOutError as NSError {
completion(true)
}
completion(false)
}

Firebase check if account is disabled

I am currently trying to create a function that automatically detects if an account has been disabled or not.
By this, I mean that I want the user to be logged out automatically, and not have the permission to do anything.
I know the stuff about permissions are done in firebase security & rules. However, I have no idea on how to disallow activity from disabled accounts. I am only familiar with the auth.uid and variables.
How should I proceed if I want to do this?
I have played with the idea of re-authenticating the user for each form it proceeds to, but I quickly figured out that this would be unnecessary use of data.
Or is this the way to go?
One of the way is use of authentication. You need to re-authenticate the firebase user:
user?.reauthenticateWithCredential(credential) { error in
if let error = error {
// An error happened.
} else {
// User re-authenticated.
}
}
You get FIRAuthErrorCodeUserDisabled error if account is disabled.
Run this in a loop at some time-interval.
I reloaded the current User, if it's disabled you will get an error:
if let userInfo = Auth.auth().currentUser {
userInfo.reload(completion: { (error) in
guard error == nil else {
debugPrint(error.debugDescription)
return
}
})
}
Your asked a while ago, hope answer still helps:
You can add an observer for authStateDidChange, so it got fired if user logged out
NotificationCenter.default.addObserver(forName: NSNotification.Name.AuthStateDidChange, object: Auth.auth(), queue: nil) { _ in
if ((Auth.auth().currentUser) == nil) {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let mvc = storyboard.instantiateViewController(withIdentifier: "MainViewController")
self.window!.rootViewController = mvc
let options: UIView.AnimationOptions = .transitionCrossDissolve
let duration: TimeInterval = 0.1
UIView.transition(with: self.window!, duration: duration, options: options, animations: {}, completion:
{ completed in
self.window?.rootViewController?.performSegue(withIdentifier: "showWelcomeView", sender: nil)
})
}
}
The cool thing now is if you call the reload command, like MegaChan mentioned before, and the user got disabled or deleted, the firebase controller logs the user out, the observer fires authstate did change and your login screen, in my case welcomeviewcontroller shows up ;)