Alamofire Authentication Using Old Credentials - ios

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)

Related

the UI is not update After i call the logout method

need your support I've been struggling with this for days. I'll appreciate any help
hi
I'm trying to learn IOS Development & No SQL Database
using Firebase
by creating a Chat app using Firebase real time database & swift
my problem is UI is not updated unless i rebuild The App
sum up
-the log out functionality will not work unless i rebuild the App again ( if i sign out i can login but i cant see the Chat Messages unless i rebuild the App)
if i want to login (to see the Chat Messages) this is my Process
A- log out from the APP
B -login to the APP (here i need to be logged in ) i rebuild the App again
so every time i add a message i need to rebuild because so the signin method will get activates
the Login Method
extension FCViewController : FUIAuthDelegate
{
func authUI(_ authUI: FUIAuth, didSignInWith authDataResult: AuthDataResult?, error: Error?)
{
if (error != nil)
{
return
}
login()
}
}
func login(
let authUI = FUIAuth.defaultAuthUI()
let googleAuthProvider = FUIGoogleAuth(authUI: authUI!)
let provider : [FUIAuthProvider] = [googleAuthProvider , FUIEmailAuth()]
authUI?.providers = provider
_authHandle = Auth.auth().addStateDidChangeListener
{
(auth : Auth , user : User?) in
self.messages.removeAll(keepingCapacity: false)
self.messagesTable.reloadData()
if let activeUser = user
{
if (self.user != activeUser)
{
self.user = activeUser
self.signedInStatus(isSignedIn: true)
let name = user!.email?.components(separatedBy: "#")[0]
self.displayName = name!
print("The first call",Auth.auth().currentUser as Any)
}
else
{
// user must Sign in
self.signedInStatus(isSignedIn: false
self.loginSession()
}
}
}
}
the library
import UIKit
import Firebase
import FirebaseEmailAuthUI
import FirebaseAuthUI
import FirebaseGoogleAuthUI
the signout Method
#IBAction func signOut
try Auth.auth().signOut()
the UI
func showAlert(title: String, message: String) {
DispatchQueue.main.async {
let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
let dismissAction = UIAlertAction(title: "Dismiss", style: .destructive, handler: nil)
alert.addAction(dismissAction)
self.present(alert, animated: true, completion: nil)
}
}
When I tap the "signOut" button, the method gets called and gets executed. after the try Auth.auth().signOut() line of code gets executed, the current user is nil
(lldb) po Auth.auth().currentUser
nil
And if i login again
the Message will not Appear unless i rebuild the App

sign up flow segues to the wrong view controller and doesn't write to firestore either

So my goal is to have the correct user sign up and be shown the correct segue as well as the user info be written to Firestore. So I have a basic sign up function that gets triggered when the sign up button is pressed:
#IBAction func schoolSignupPressed(_ sender: UIButton) {
let validationError = validateFields()
let schoolName = schoolNameTextField.text!.trimmingCharacters(in: .whitespacesAndNewlines)
let schoolEmail = schoolEmailTextField.text!.trimmingCharacters(in: .whitespacesAndNewlines)
let schoolPassword = schoolPasswordTextField.text!.trimmingCharacters(in: .whitespacesAndNewlines)
let schoolID = schoolIDTextField.text!.trimmingCharacters(in: .whitespacesAndNewlines)
let schoolDistrict = schoolDistrictTextField.text!.trimmingCharacters(in: .whitespacesAndNewlines)
let dateCreated = Date()
if validationError != nil {
return
}
Auth.auth().createUser(withEmail: schoolEmail, password: schoolPassword) { (result, error) in
guard let signUpError = error?.localizedDescription else { return }
guard error == nil else {
self.showAlert(title: "Error Signing Up", message: "There was an error creating the user. \(signUpError)")
return
}
let db = Firestore.firestore()
guard let result = result else { return }
db.document("school_users/\(result.user.uid)").setData(["school_name":schoolName,
"school_id":schoolID,
"emailAddress": result.user.email ?? schoolEmail,
"remindersPushNotificationsOn": true,
"updatesPushNotificationsOn": true,
"schoolDistrict":schoolDistrict,
"time_created":dateCreated,
"userID": result.user.uid],
merge: true) { (error) in
guard let databaseError = error?.localizedDescription else { return }
guard error == nil else {
self.showAlert(title: "Error Adding User Info", message: "There was an error adding the user info. \(databaseError)")
return
}
}
let changeRequest = result.user.createProfileChangeRequest()
changeRequest.displayName = schoolName
changeRequest.commitChanges { (error) in
guard error == nil else {
return
}
print("School Name Saved!")
}
DispatchQueue.main.asyncAfter(deadline: .now()+1) {
self.performSegue(withIdentifier: Constants.Segues.fromSchoolSignUpToSchoolDashboard, sender: self)
}
}
}
This is the sign up function for the 'school' user, but the 'student' user is essentially the same thing just different fields and of course a different segue destination. Now maybe like a day ago or 2, I was testing this function out and it was working completely fine the user was succesfully signed up, the user info was written to firestore, the correct view controller was displayed, the only difference was I had some DispatchGroup blocks within the function because when i was running the method in TestFlight, there would be a couple of bugs that would crash the app.
So I figured since everything was working fine in the simulator, I archive the build, upload it to TestFlight and wait for it to be approved. It got approved last night and I ended up testing it out on my phone this morning to see it again, now when I try to sign up as either a school user or a student user, it segues to the wrong view controller every time and no info gets written to firestore, the user just gets saved in Firebase Auth and that is not the outcome I expect in my app.
I've checked the segue identifiers, I've checked the connections tab, and even though it was working amazing 24 hours ago, I still checked it all. I'm trying my best to really appreciate what Apple does for developers but I'm really starting to grow a hatred towards TestFlight, everything I do and run in the simulator works fantastic on Xcode, as soon as I run it in TestFlight, everything just goes out the window. I hate these types of bugs because you genuinely don't know where the issue is stemming from simply because you've used, if not very similar, the exact same method in every other previous situation.
The login process works fine on both student and school user, I'll show an example of the school user login method:
#IBAction func loginPressed(_ sender: UIButton) {
let validationError = validateFields()
let email = schoolEmailTextField.text!.trimmingCharacters(in: .whitespacesAndNewlines)
let password = schoolPasswordTextField.text!.trimmingCharacters(in: .whitespacesAndNewlines)
if validationError != nil {
return
} else {
Auth.auth().signIn(withEmail: email, password: password) { (result, error) in
guard let signInError = error?.localizedDescription else { return }
let group = DispatchGroup()
group.enter()
guard error == nil else {
self.showAlert(title: "Error Signing In", message: "There was an issue trying to sign the user in. \(signInError)")
return
}
group.leave()
group.notify(queue: .main) {
DispatchQueue.main.asyncAfter(deadline: .now()+1) {
self.performSegue(withIdentifier: Constants.Segues.fromSchoolLoginToSchoolEvents, sender: self)
}
}
}
}
}
Pretty much the same for student users. If anyone can point out possible issues for this bug in the first code snippet that would be amazing. Thanks in advance.
Although it is helpful, removing the error.localizedDescription line brought everything back to normal.

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.

PFFacebookUtils. How to make difference between login and signup?

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.

How to persist Firebase authdata after Force Quit (iOS)

I am using email + password authentication with Firebase for my app. Login works, and I use observeAuthEventWithBlock to check if a user is logged in - in order not to bring up the Login page. If I press the home button and open the app again, there is no problem. The problem I am having is if I force-quit the app. When I re-open, I have to log-in again.
Some notes about the setup before I show my login code.
There is a LoginViewController - not embedded - built w/ Storyboard
This is connected to a Navigation Controller
Which is what the first screen is embedded in, and the rest of the app uses this Nav Controller.
Login code:
#IBAction func loginButtonPressed() {
let userEmail = emailTextField.text
self.ref.authUser(self.emailTextField.text, password: self.passwordTextField.text, withCompletionBlock: { (error, auth) -> Void in
guard error == nil else {
if let errorCode = FAuthenticationError(rawValue: error.code) {
switch (errorCode) {
case .EmailTaken:
self.displayMessage("Email Error", theMessage: "This email is taken")
case .InvalidEmail:
self.displayMessage("Email Error", theMessage: "This email is invalid")
case .UserDoesNotExist:
self.displayMessage("User Error", theMessage: "A user account for email: \(userEmail!) does not exist")
case .InvalidPassword:
self.displayMessage("Password Error", theMessage: "The password is incorrect")
case .NetworkError:
self.displayMessage("Network Error", theMessage: "Seems like there's a problem with your internet connection")
default:
return
}
}
return //set Unknown Error Alert here
}
print("LOGGED IN: segue from loginButtonPressed")
self.userLoggedIn = true
print("user is logged in? \(self.userLoggedIn)")
self.performSegueWithIdentifier("loginLocaleSegue", sender: self)
})
}
Check if user is logged in - if so segue to navcon, pop it and display embedded View Controller:
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
if self.userLoggedIn.boolValue == true {
ref.observeAuthEventWithBlock { (authData) -> Void in
if authData != nil {
let navCon: UINavigationController = UIStoryboard(name: "Main", bundle: nil).instantiateViewControllerWithIdentifier("MainNavigationController") as! UINavigationController
self.presentViewController(navCon, animated: false, completion: nil)
navCon.popViewControllerAnimated(false)
print("user is authenticated: \(authData.providerData["email"] as! String)")
print("segues from viewDidAppear")
} else {
return
}
}
}
}
I've seen questions related to Firebase auth which state that Authdata is stored in Keychain by default, which causes problems with Authdata persisting even after deletion of app, but I'm experiencing the total opposite issue. Any ideas?
From what I can tell, you're not writing "self.userLoggedIn = true" to any database, so it makes sense that it continues being "True" while you have the app on idle, but once you close the application, it then becomes nil (no value), this is because it's just chilling in the background while the app is open, but not completely closed. Try writing this to your Firebase database, as outlined in this tutorial, and see if that helps.
https://www.raywenderlich.com/109706/firebase-tutorial-getting-started

Resources