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.
Related
I am using Alamofire to sign into my app. The username and password come from text fields. I can sign in fine but if I sign out and return to the log in screen each subsequent log in attempt uses the original credentials. For example, if I enter 'test#test.com' and 'password', I can get in but then if I sign out and enter 'test2#test.com' and 'test2password', it uses the first credentials for 'test#test.com'. Also, if I enter incorrect credentials, it will always say they incorrect even after I enter the correct credentials. The only way to get it to accept a different set of credentials is to force close the app and reopen it. The other part of this is that each subsequent call to other endpoints after the sign in requires user credentials. That all works fine once I sign in, but when I fix the log in issue by using an authorization header and not the Alamofire authenticate method, my subsequent calls don't work.
Here is how I'm trying to sign in so all of my subsequent calls work but this causes the first set of credentials to be used every time until I force close the app.
Alamofire.request("https://example.com/signin/", method: .get).authenticate(user: userName, password: password).responseJSON { response in
if response.result.value != nil {
let results = response.result.value as? [[String: AnyObject]]
if results!.count > 0 {
if let dictionary = results?[0] {
if let userEmail = dictionary["email"] {
print("Signed in with: \(userEmail)")
sharedUser.userJSON = JSON(response.result.value!)
sharedUser.userEmail = self.usernameField.text!
sharedUser.userPassword = self.passwordField.text!
DispatchQueue.main.async {
self.performSegue(withIdentifier: "signInSegue", sender: nil)
}
}
}
} else {
DispatchQueue.main.async {
let failedSignInAlert = UIAlertController(title: "Invalid Email or Password", message: "The information you entered is incorrect. Please verify you have the correct information and try again.", preferredStyle: .alert)
let failedAction = UIAlertAction(title: "OK", style: .default, handler: { (action) in
let cookieStorage = HTTPCookieStorage.shared
for cookie in cookieStorage.cookies! {
cookieStorage.deleteCookie(cookie)
}
let urlCache = URLCache.shared
urlCache.removeAllCachedResponses()
self.dismiss(animated: true, completion: nil)
})
failedSignInAlert.addAction(failedAction)
self.present(failedSignInAlert, animated: true, completion: nil)
}
}
} else {
print("SIGN IN FAILED!")
}
DispatchQueue.main.async {
self.loadingIndicator.stopAnimating()
self.signInButton.isEnabled = true
}
}
By default, .authenticate uses a URLCredential.Storage value of .forSession, which means that the credential will be used for all auth challenges in a session automatically, and Alamofire doesn't get a chance to provide it's new URLCredential. Passing the value of .none may fix your issue.
.authenticate(user: userName, password: password, persistence: .none)
I am creating an application which authenticate user using PhoneAuth. In my application I have a function which let user add Email to his account But not meant that I authenticate user using Email and Password, I just want to add email to his/her account (auth.auth().currentUser).
Initially, I let user to add his/her email in textfield and then I start to logout user from his/her device in order to reauthentication otherwise, I cannot update user's email using auth.updateEmail(). But sadly, the credential always expired after I called func updateEmail().
This is how I signIn user and update Email
let credential = PhoneAuthProvider.provider().credential(withVerificationID: verficationID, verificationCode: code)
Auth.auth().signInAndRetrieveData(with: credential) { (result, error) in
guard let result = result else {
completion(false)
return
}
if error == nil {
guard let user = Auth.auth().currentUser else {
return
}
if UserDefaults.standard.getUserUpdatedEmail() {
user.reauthenticate(with: credential, completion: { (error) in
if error == nil {
user.updateEmail(to: newEmail, completion: { (error) in
if error == nil {
UserDefaults.standard.setUserUpdatedEmail(value: false)
completion(true)
//return true
} else {
completion(false)
//return false
print("Error validate email ",error?.localizedDescription ?? "")
}
})
} else {
completion(false)
// return false
print("Error reauthntication email ",error?.localizedDescription ?? "")
}
})
} else {
print("User is signed in \(result.user)")
print("This is userID \(result.user.uid)")
completion(true)
}
} else {
if let error = error {
print("Error during verification \(error.localizedDescription)")
}
completion(false)
}
}
I don't why the credential is expired too fast? I cannot figure it out how to update user email using PhoneAuthCredential. Is there any other techniques to do it?
You are trying to re-use the same phone credential (you used it first to sign-in). The phone credential is one time use only. If you want to update email immediately after sign-in, re-auth is not needed. However, if you try to update email after some time, you will need to send a new SMS code to re-authenticate.
I have created a very basic sign in app in swift to practice firebase. I've come up with this:
#IBAction func signInPressed(_ sender: UIButton) {
//Assigns and checks if the email and password aren't empty
if let inpt_email = emailField.text, let inpt_password = passwordField.text {
Auth.auth().signIn(withEmail: inpt_email, password: inpt_password, completion: { (user, error) in
//Checks if the user exists
if error != nil {
//ERROR: No user found
self.signInLabel.text = "Invalid User! Please Try Again"
} else {
//Sign Success
self.performSegue(withIdentifier: "toHome", sender: self)
}
})
}
} //End of signInPressed
// END: SIGN IN BUTTON
The //Sign Success part doesn't actually get any data at all, it just checks if the input matches any User that is registered in Firebase, and then segue to the next page. What I want to do is to:
Get the uid of the user which matches both the emailField and passwordField in the Firebase Auth.
Somewhat registers that uid as "Currently Signed In" in the app itself for future reference.
I tried reading the Firebase Documentation and all I got was this:
if Auth.auth().currentUser != nil {
// User is signed in.
// ...
} else {
// No user is signed in.
// ...
}
And this:
let user = Auth.auth().currentUser
if let user = user {
// The user's ID, unique to the Firebase project.
// Do NOT use this value to authenticate with your backend server,
// if you have one. Use getTokenWithCompletion:completion: instead.
let uid = user.uid
let email = user.email
let photoURL = user.photoURL
// ...
}
I'm new to Firebase so I basically don't understand how to use this, although I kind of get what it means, I just don't know what it's for or how to put it in action.
Thanks!
try this
if let user = user {
print(user.uid)
}
self.performSegue(withIdentifier: "toHome", sender: self)
When calling the code below I get the following error message
"There is no user record corresponding to this identifier. The user may have been deleted."
Isn't the user created by the code above at that point?
I am trying to validate the new user's email using a verification email after its creation.
Thanks
let saveAction = UIAlertAction(title: "Create",
style: .default) { action in
let emailField = alert.textFields![0]
let passwordField = alert.textFields![1]
Auth.auth().createUser(withEmail: emailField.text!,
password: passwordField.text!) { user, error in
if error == nil {
Auth.auth().signIn(withEmail: self.textFieldLoginEmail.text!,
password: self.textFieldLoginPassword.text!)
}
}
Auth.auth().currentUser?.sendEmailVerification { (error) in
if let error = error
{print("Error when sending Email verification is \(error)")}
}
}
When you create a user, they're automatically signed in. So you can remove the sign-in call and move the sending of the verification email into the completion handler:
Auth.auth().createUser(withEmail: emailField.text!,
password: passwordField.text!) { user, error in
if error == nil {
Auth.auth().currentUser?.sendEmailVerification { (error) in
if let error = error
....
In cases where that won't work, the sign-in method also has a completion handler, so:
Auth.auth().signIn(withEmail: email, password: password) { (user, error) in
if error == nil {
Auth.auth().currentUser?.sendEmailVerification { (error) in
if let error = error
// ...
}
I'm trying to provide some very simple (as I thought) functionality into my application which uses Parse.com service. What I need is just allow users to create an account via Facebook and login them again via Facebook.
The problem is that PFFacebookUtils login methods not only login users through Facebook but also create a new PFUser. Why is it a problem for me? Well, of course. I can distinguish between signing up and in by isNew field but it doesn't really help.
Consider the following - user tries to login via Facebook (he doesn't any have PFUser yet), he loggs in, a new user is created. I see that the user is new (i.e. the user wasn't registered before) and I have to reject this login. Ok, I reject him, I say "You haven't been registered yet, go and sign up". User signs up (via the same login method) and this time the same PFUser is returned which was created when the user tried to log in. I see that the user is not new, it has already been registered and therefore I have to reject the user again, because the account already exists and it is impossible to create the same account again.
Do you understand the problem? Am I being idiotic not realizing how to deal with PFFacebookUtils account creation and logging in or it is PFFacebookUtils who provides an idiotic API? How do you people do that? How do you solve the problem that I've described. Really, it must be so simple but I can't find a good example anywhere
I have login and signup code in swift that checks to see if a user is new in login and signup. Here is my code:
LOGIN
let spinningActivity = MBProgressHUD.showHUDAddedTo(self.view, animated: true)
spinningActivity.label.text = "Just a Moment"
spinningActivity.detailsLabel.text = "Logging in"
if reachabilityStatus == kNOTREACHABLE {
spinningActivity.hideAnimated(true)
self.displayError("No Internet Connection", message: "Please connect to the internet before continuing")
} else {
let permissions = ["public_profile"]
PFFacebookUtils.logInInBackgroundWithReadPermissions(permissions) { (user:PFUser?, error:NSError?) -> Void in
if error != nil {
spinningActivity.hideAnimated(true)
self.displayError("Error", message: error!.localizedDescription)
} else if let user = user {
if user.isNew {
spinningActivity.hideAnimated(true)
PFUser.currentUser()?.deleteInBackground()
self.displayNoticeWithTwoActions("Account Not Found", message: "This Facebook account is not in our system. You have to sign up first.", firstButtonTitle: "Sign Up",closeButtonTitle: "Ok", segue: "dontHaveAccountSegue")
} else {
spinningActivity.hideAnimated(true)
self.performSegueWithIdentifier("successfulLoginSegue", sender: self)
}
} else {
PFUser.currentUser()?.deleteInBackground()
spinningActivity.hideAnimated(true)
self.displayError("Error", message: "Unless you tapped on 'Cancel' or 'Done', something went wrong. Please try again.")
}
}
}
SIGNUP
I have a signup button and then a function that is implemented into the login button called "loadFacebookUserDetails"
let spinningActivity = MBProgressHUD.showHUDAddedTo(self.view, animated: true)
spinningActivity.label.text = "Just a Moment"
spinningActivity.detailsLabel.text = "Loading Details"
if reachabilityStatus == kNOTREACHABLE {
spinningActivity.hideAnimated(true)
self.displayError("No Internet Connection", message: "Please connect to the internet before continuing")
} else {
let permissions = ["public_profile", "email"]
PFFacebookUtils.logInInBackgroundWithReadPermissions(permissions) { (user:PFUser?, error:NSError?) -> Void in
if let user = user {
if !user.isNew {
spinningActivity.hideAnimated(true)
PFUser.logOut()
self.displayNoticeWithTwoActions("Account Found", message: "This Facebook account already in our system. You have to log in first.", firstButtonTitle: "Log In", closeButtonTitle: "Cancel", segue: "haveAccountSegue")
} else if error != nil {
spinningActivity.hideAnimated(true)
self.displayError("Error", message: error!.localizedDescription)
} else if error == nil {
spinningActivity.hideAnimated(true)
self.loadFacebookUserDetails()
}
}
else {
spinningActivity.hideAnimated(true)
self.displayError("Something Went Wrong", message: "Unless you tapped on 'Cancel' or 'Done', something went wrong. Please try again")
}
}
}
func loadFacebookUserDetails() {
let spinningActivity = MBProgressHUD.showHUDAddedTo(self.view, animated: true)
spinningActivity.mode = MBProgressHUDMode.AnnularDeterminate
spinningActivity.label.text = "Just a Moment"
spinningActivity.detailsLabel.text = "Loading Details"
let requestPerameters = ["fields": "id, email, first_name, last_name, name"]
let userDetails = FBSDKGraphRequest(graphPath: "me", parameters: requestPerameters)
userDetails.startWithCompletionHandler { (connection, result, error:NSError!) -> Void in
if error != nil {
spinningActivity.hideAnimated(true)
self.displayError("Error", message: error!.localizedDescription)
PFUser.logOut()
} else {
let userID:String = result["id"] as! String
let userEmail:String = result["email"] as! String
let userFirstName:String = result["first_name"] as! String
let userLastName:String = result["last_name"] as! String
// Get Facebook Profile Picture
let userProfile = "https://graph.facebook.com/" + userID + "/picture?type=large"
let usernameLink = "https://graph.facebook.com/" + userID
let username = usernameLink.stringByReplacingOccurrencesOfString("https://graph.facebook.com/", withString: "")
let profilePictureUrl = NSURL(string: userProfile)
let profilePictureData = NSData(contentsOfURL: profilePictureUrl!)
if profilePictureData != nil {
let profilePictureObject = PFFile(data: profilePictureData!)
PFUser.currentUser()?.setObject(profilePictureObject!, forKey: "profile_picture")
}
PFUser.currentUser()?.setObject(userFirstName, forKey: "first_name")
PFUser.currentUser()?.setObject(userLastName, forKey: "last_name")
PFUser.currentUser()?.setObject(username, forKey: "facebook_link")
if userEmail == userEmail {
PFUser.currentUser()?.email = userEmail
}
PFUser.currentUser()?.saveInBackgroundWithBlock({ (success:Bool, error:NSError?) -> Void in
if error != nil {
spinningActivity.hideAnimated(true)
self.displayError("Error", message: error!.localizedDescription)
PFUser.logOut()
} else if success == true {
if !userID.isEmpty {
spinningActivity.hideAnimated(true)
NSUserDefaults.standardUserDefaults().setObject("authData", forKey: "facebookAuth")
NSUserDefaults.standardUserDefaults().synchronize()
self.performSegueWithIdentifier("facebookUserDetailsSegue", sender: self)
}
} else {
spinningActivity.hideAnimated(true)
self.displayError("Something Went Wrong", message: "Please try again")
PFUser.logOut()
}
})
}
}
}
If you have trouble with the conversion to objective c, I bet you can find YouTube videos on how to do this.