I am trying to setup a method to allow users to login to their Firebase accounts using Facebook. If it is a first time user creating an account everything works fine, but if a user made an account before and is trying to login in to their account is where the trouble begins. When the code
FIRAuth.auth()?.signIn(with: credential, completion: {(user, error)
runs I get the error saying that the email is already in use, even though it is the account in which they are trying to sign in for.
My entire code is here:
func handleCustomFBLogin() {
FBSDKLoginManager().logIn(withReadPermissions: ["email"], from: self) { (result, err) in
if err != nil {
print("Error loggin in is \(err)")
//self.facebookanimateIn()
} else if (result?.isCancelled)!{
print("The user cancelled loggin in ")
} else {
let credential = FIRFacebookAuthProvider.credential(withAccessToken: FBSDKAccessToken.current().tokenString)
let graphRequest : FBSDKGraphRequest = FBSDKGraphRequest(graphPath: "me", parameters: ["fields": "id, name, email"])
graphRequest.start(completionHandler: { (connection, result, error) -> Void in
if ((error) != nil) {
print("Error: \(error)")
} else {
let data:[String:AnyObject] = result as! [String: AnyObject]
let facebookName:NSString = data["name"] as! NSString
let facebookEmail = data["email"] as Any
let userId = data["id"] as! NSString
let facebookProfileUrl = "http://graph.facebook.com/\(userId)/picture?type=large"
let facebookAge = data["age_range"] as Any
let password = "needToPutRandomizedPasswordInHere" as String
FIRAuth.auth()?.createUser(withEmail: facebookEmail as! String, password: password, completion: {result, error in
if error != nil{
//user has account, they just need to sign in
FIRAuth.auth()?.signIn(with: credential, completion: {(user, error) in
if error != nil{
print(error.debugDescription)
return
}
let storyboard: UIStoryboard = UIStoryboard(name: "Main", bundle:nil)
let vc: UINavigationController = storyboard.instantiateViewController(withIdentifier: "checker") as! UINavigationController
self.present(vc, animated: true, completion: nil)
// ref.removeObserver(withHandle: handle)
})
} else {
//user does not have an account and they need to create one
guard let uid = result?.uid else{
return
}
print("user created as \(uid)")
let val = "0"
let number = (val as NSString).integerValue
let ref = FIRDatabase.database().reference()
let usersReference = ref.child("Users").child(uid)
let values = [""] as [String : Any];
usersReference.updateChildValues(values, withCompletionBlock: { (err,ref) in
if err != nil {
print(err.debugDescription)
return}})
print("Save the user successfully into Firebase database")
let storyboard: UIStoryboard = UIStoryboard(name: "Main", bundle:nil)
let vc: UINavigationController = storyboard.instantiateViewController(withIdentifier: "checker") as! UINavigationController
self.present(vc, animated: true, completion: nil)
}
})
}
})
}
}
}
once you have created the user, all you need is the login step
FIRAuth.auth()?.signIn(withEmail: email, password: password, completion: { (user, error) in
if error != nil {
// handle errors
} else {
// continue with login process
}
})
Related
Once the user signs in with Google, I want to take them to the home screen; however, the code does not fully execute this.
This is the code:
func sign(_ signIn: GIDSignIn!, didSignInFor user: GIDGoogleUser!,
withError error: Error!) {
if let error = error {
if (error as NSError).code == GIDSignInErrorCode.hasNoAuthInKeychain.rawValue {
print("The user has not signed in before or they have since signed out.")
} else {
print("\(error.localizedDescription)")
}
return
}
let firstName = user.profile.givenName
let lastName = user.profile.familyName
let email = user.profile.email
//Firebase sign in
guard let authentication = user.authentication else {return}
let credential = GoogleAuthProvider.credential(withIDToken: authentication.idToken, accessToken: authentication.accessToken)
Auth.auth().signIn(with: credential) { (authResult, error) in
if let error = error {
print("Firebase sign In error")
print(error)
return
} else {
let db = Firestore.firestore()
db.collection("users").addDocument(data: ["firstName": firstName!, "lastName": lastName!, "email": email!, "uid": authResult!.user.uid]) { (error) in
if error != nil {
print("Error: User data not saved")
}
}
print("User is signed in with Firebase")
let mainStoryboard:UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let homeViewController = mainStoryboard.instantiateViewController(withIdentifier: "HomeVC") as! HomeViewController
self.window?.rootViewController = homeViewController
self.window?.makeKeyAndVisible()
}
}
}
More specifically:
Auth.auth().signIn(with: credential) { (authResult, error) in
if let error = error {
print("Firebase sign In error")
print(error)
return
} else {
let db = Firestore.firestore()
db.collection("users").addDocument(data: ["firstName": firstName!, "lastName": lastName!, "email": email!, "uid": authResult!.user.uid]) { (error) in
if error != nil {
print("Error: User data not saved")
}
}
print("User is signed in with Firebase")
let mainStoryboard:UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let homeViewController = mainStoryboard.instantiateViewController(withIdentifier: "HomeVC") as! HomeViewController
self.window?.rootViewController = homeViewController
self.window?.makeKeyAndVisible()
}
}
the print("User is signed in with Firebase") does take place but it fails to switch the HomeViewController and I'm not sure what it is that I am doing wrong here.
The problem in the code is that Firebase is asynchronous. So this section
} else {
let db = Firestore.firestore()
db.collection("users").addDocument(data: ["firstName": firstName!, "lastName": lastName!, "email": email!, "uid": authResult!.user.uid]) { (error) in
if error != nil {
print("Error: User data not saved")
}
}
print("User is signed in with Firebase")
This print statement
print("User is signed in with Firebase")
will execute before the code within the closure. The reason is the code is faster than the internet - you have to 'wait' for data to be returned from Firebase before moving forward in your code. To fix, move the statements inside the closure so they execute when Firebase returns
} else {
let db = Firestore.firestore()
db.collection("users").addDocument(data: ["firstName": firstName!, "lastName": lastName!, "email": email!, "uid": authResult!.user.uid]) { (error) in
if error != nil {
print("Error: User data not saved")
return
}
print("User is signed in with Firebase")
let mainStoryboard:UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let homeViewController = mainStoryboard.instantiateViewController(withIdentifier: "HomeVC") as! HomeViewController
self.window?.rootViewController = homeViewController
self.window?.makeKeyAndVisible()
}
Note that with this code flow, every time an existing user signs it, it will create a new document in the users collection. That's probably not the intention.
My guess is that window is nil and that's why your code isn't executing properly. Try the code below if you are targeting iOS 13. Ideally, you would want to move the Sign-in code inside SceneDelegate and duplicate it in AppDelegate if your app also supports iOS 12, 11 etc.
Auth.auth().signIn(with: credential) { (authResult, error) in
if let error = error {
print("Firebase sign In error")
print(error)
return
} else {
let db = Firestore.firestore()
db.collection("users").addDocument(data: ["firstName": firstName!, "lastName": lastName!, "email": email!, "uid": authResult!.user.uid]) { (error) in
if error != nil {
print("Error: User data not saved")
}
}
print("User is signed in with Firebase")
DispatchQueue.main.async {
let scene = UIApplication.shared.connectedScenes.first
if let sceneDelegate = scene?.delegate as? SceneDelegate {
let window = sceneDelegate?.window
let mainStoryboard:UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let homeViewController = mainStoryboard.instantiateViewController(withIdentifier: "HomeVC") as! HomeViewController
window?.rootViewController = homeViewController
window?.makeKeyAndVisible()
}
}
}
}
I'm trying to log users into into my app using Facebook and then save the users into my Cloud Firestore. I'm just not too sure how to go about it. This is the code I have now
#IBAction func buttTapped(_ sender: Any) {
let fbLoginManager = LoginManager()
fbLoginManager.logIn(permissions: ["public_profile", "email"], from: self) { (result, error) in
if let error = error {
print("Failed to login: \(error.localizedDescription)")
return
}
guard let accessToken = AccessToken.current else {
print("Failed to get access token")
return
}
let credential = FacebookAuthProvider.credential(withAccessToken: accessToken.tokenString)
// Perform login by calling Firebase APIs
Auth.auth().signIn(with: credential, completion: { (user, error) in
if let error = error {
print("Login error: \(error.localizedDescription)")
let alertController = UIAlertController(title: "Login Error", message: error.localizedDescription, preferredStyle: .alert)
let okayAction = UIAlertAction(title: "OK", style: .cancel, handler: nil)
alertController.addAction(okayAction)
self.present(alertController, animated: true, completion: nil)
return
}
// Present the main view
self.dismiss(animated: true, completion: nil)
})
}
}//
What code do I have to add to grab certain values like email and name and save it to Cloud Firestore the image below is an example for what I have using email signup
You just done the Facebook authentication and get the access token. To get user public info you need a graph request. The full Facebook Authentication and Graph request code will be like
func facebookLogin(){
let loginManager = LoginManager()
loginManager.logIn(permissions: [.publicProfile, .email], viewController: self) { loginResult in
switch loginResult {
case .failed(let error):
print(error)
case .cancelled:
print("User cancelled login.")
case .success(let grantedPermissions, let declinedPermissions, let accessToken):
print("Logged in!")
self.fetchUserProfile()
}
}
}
func fetchUserProfile() {
let graphRequest : GraphRequest = GraphRequest(graphPath: "me", parameters: ["fields":"id, email, name, picture.width(480).height(480)"])
graphRequest.start(completionHandler: { (connection, result, error) -> Void in
if ((error) != nil)
{
print("Error took place: \(error ?? "" as! Error)")
}
else
{
let result = result as! Dictionary<String, Any>
let picture = result["picture"] as! Dictionary<String, Any>
let dataObj = picture["data"] as! Dictionary<String,Any>
var emailValue = result["email"] as! String?
if emailValue == nil {
emailValue = ""
}
let name = result["name"] as? String
let id = result["id"] as? String
let imageUrl = result["url"] as? String
// Update data to firestore
}
})
}
Hope you understand.
I have issues with loading user photo from facebook.
I want to fetch facebook user photo to parse data base.
My code:
let permissions:[String] = ["public_profile", "email"]
PFFacebookUtils.logInInBackground(withReadPermissions: permissions) { (user, error) in
if user == nil {
NSLog("Uh oh. The user cancelled the Facebook login.")
} else if user!.isNew {
NSLog("User signed up and logged in through Facebook!")
self.loadData()
} else {
NSLog("User logged in through Facebook!")
}
}
func loadData(){
let fbRequest = FBSDKGraphRequest(graphPath: "me", parameters: nil)
fbRequest?.start(completionHandler: { (FBSDKGraphRequestConnection, result, error) in
if error == nil{
if let dict = result as? Dictionary<String, AnyObject>{
let name:String = dict["name"] as AnyObject? as! String
let facebookID:String = dict["id"] as AnyObject? as! String
let email:String = dict["email"] as AnyObject? as! String
let pictureURL = "https://graph.facebook.com/\(facebookID)/picture?type=large&return_ssl_resources=1"
let URLRequest = NSURL(string: pictureURL)
let URLRequestNeeded = NSURLRequest(url: URLRequest! as URL)
NSURLConnection.sendAsynchronousRequest(URLRequestNeeded as URLRequest, queue: OperationQueue.main, completionHandler: { (response, data, error) in
if error == nil {
let picture = PFFile(data: data!)
PFUser.current()?.setObject(picture!, forKey: "profilePicture")
PFUser.current()?.saveInBackground()
}
else {
print("Error: \(String(describing: error?.localizedDescription))")
}
})
PFUser.current()!.setValue(name, forKey: "username")
PFUser.current()!.setValue(email, forKey: "email")
PFUser.current()!.saveInBackground()
}
}
})
}
But i all time i have error message, and in data base i have empty row.
How i can fix it?
After trying many methods, this is what works for me.
On AppDelegate, import Parse, ParseFacebookUtilsV4 and FBSDKCoreKit
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool
{
PFFacebookUtils.initializeFacebook(applicationLaunchOptions: launchOptions)
FBSDKApplicationDelegate.sharedInstance().application(application, didFinishLaunchingWithOptions: launchOptions)
return true
}
and add two methods
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
return FBSDKApplicationDelegate.sharedInstance().application(
app,
open: url as URL?,
sourceApplication: options[UIApplication.OpenURLOptionsKey.sourceApplication] as? String,
annotation: options[UIApplication.OpenURLOptionsKey.annotation]
)
}
func application(_ application: UIApplication, open url: URL, sourceApplication: String?, annotation: Any) -> Bool {
return FBSDKApplicationDelegate.sharedInstance().application(
application,
open: url as URL?,
sourceApplication: sourceApplication,
annotation: annotation)
}
And then, on login ViewController:
#IBAction func signInFacebook(_ sender: Any)
{
let permissions = ["public_profile", "email"]
PFFacebookUtils.logInInBackground(withReadPermissions: permissions) { (user : PFUser?, error: Error?) in
if(error != nil)
{
//Display an alert message
let myAlert = UIAlertController(title:"Alert", message:error?.localizedDescription, preferredStyle: UIAlertController.Style.alert)
let okAction = UIAlertAction(title: "Ok", style: UIAlertAction.Style.default, handler: nil)
myAlert.addAction(okAction)
self.present(myAlert, animated:true, completion:nil);
return
}
else
{
let query = PFQuery(className: "_User")
query.whereKey("email", equalTo: NSNull())
query.findObjectsInBackground (block: { (objects, error) -> Void in
if error == nil
{
// found related objects
for object in objects! {
//delete error user
print(object.value(forKey: "username")!)
object.deleteEventually()
}
}
else{
print(error!.localizedDescription)
}
}
)}
self.indicator.isHidden = false
self.indicator.startAnimating()
self.setUp.isHidden = false
if(FBSDKAccessToken.current() != nil) {
FBSDKGraphRequest(graphPath: "me", parameters: ["fields": "id, name, email, first_name, last_name"]).start(completionHandler: { (connection, result, error) in
let resultDic = result as! [String: Any]
let nameFB = resultDic["name"] as! String
let emailFB = resultDic["email"] as! String
let idFB = resultDic["id"] as! String
let firstFB = resultDic["first_name"] as! String
let lastFB = resultDic["last_name"] as! String
// correct in case of double First or Last name
let firstFBNoSpaces = firstFB.replacingOccurrences(of: " ", with: "_", options: .literal, range: nil)
let lastFBNoSpaces = lastFB.replacingOccurrences(of: " ", with: "_", options: .literal, range: nil)
var userNameNew = firstFBNoSpaces + "_" + lastFBNoSpaces
userNameNew = userNameNew.lowercased()
// send data to server to related columns
let user = PFUser()
user.username = userNameNew.lowercased()
user.email = emailFB.lowercased()
user.password = idFB
user["fullname"] = nameFB.capitalized
UserDefaults.standard.set(user.username, forKey: "username")
// get Facebook profile picture
let userProfile = "https://graph.facebook.com/" + idFB + "/picture?type=large"
let profilePictureUrl = NSURL(string: userProfile)
let profilePictureData = NSData(contentsOf: profilePictureUrl! as URL)
if(profilePictureData != nil)
{
let avaFile = PFFile(data:profilePictureData! as Data)
user["ava"] = avaFile
}
// will try to login with facebook
user.signUpInBackground { (success, error) -> Void in
if success
{
// remember looged user
UserDefaults.standard.set(user.username, forKey: "username")
UserDefaults.standard.set(true, forKey: "byFacebook")
// call login func from AppDelegate.swift class
let appDelegate : AppDelegate = UIApplication.shared.delegate as! AppDelegate
appDelegate.login()
self.setUp.isHidden = true
self.indicator.isHidden = true
self.indicator.stopAnimating()
}
else
{
let existing : String? = UserDefaults.standard.string(forKey: "username")
// user already exists in app database
PFUser.logInWithUsername(inBackground: existing!, password: user.password!){ (user, error) -> Void in
if error == nil
{
// remember user or save in App Memory did the user login or not
UserDefaults.standard.set(user!.username, forKey: "username")
UserDefaults.standard.set(true, forKey: "byFacebook")
// call login function from AppDelegate.swift class
let appDelegate : AppDelegate = UIApplication.shared.delegate as! AppDelegate
appDelegate.login()
self.setUp.isHidden = true
self.indicator.isHidden = true
self.indicator.stopAnimating()
}
}
}
}
})
}
else
{
self.setUp.isHidden = true
self.indicator.isHidden = true
self.indicator.stopAnimating()
}
}
}
you can try
#IBAction func loginFacebookAction(sender: AnyObject) {//action of the custom button in the storyboard
let fbLoginManager : FBSDKLoginManager = FBSDKLoginManager()
fbLoginManager.logIn(withReadPermissions: ["email"], from: self) { (result, error) -> Void in
if (error == nil){
let fbloginresult : FBSDKLoginManagerLoginResult = result!
// if user cancel the login
if (result?.isCancelled)!{
return
}
if(fbloginresult.grantedPermissions.contains("email"))
{
self.getFBUserData()
}
}else {
print(error!.localizedDescription)
// self.view.showToast(withMessage: error!.localizedDescription)
}
}
}
func getFBUserData(){
if((FBSDKAccessToken.current()) != nil){
FBSDKGraphRequest(graphPath: "me", parameters: ["fields": "id, name, first_name, last_name, picture.type(large), email"]).start(completionHandler: { (connection, result, error) -> Void in
if (error == nil){
//everything works print the user data
print(result)
// if let data = result as? [String:Any],
// let user = Mapper<User>().map(
// JSONObject: data
// ){
// AppHelper.set(currentuser: user)
// }
}else {
// self.view.showToast(withMessage: error!.localizedDescription)
print(error!.localizedDescription)
}
})
}
}
Facebook Login screen showing twice, the first one is the regular login page, but the second says "you have already authorized my app name". Can anyone tell me what I'm doing wrong in my code.
Here is my login code:
static func createAndLogin(_ viewController: UIViewController, completion: #escaping (_ success: Bool) -> Void) {
let loginManager = FBSDKLoginManager()
loginManager.logOut()
loginManager.logIn(withReadPermissions: ["public_profile", "email", "user_friends"], from: viewController) { (result, error) -> Void in
if error != nil {
print("login FAILED \(error)")
completion(false)
} else if (result?.isCancelled)!{
print("login is CANCELLED")
completion(false)
} else if FBSDKAccessToken.current() != nil {
let accessToken = FBSDKAccessToken.current().tokenString
let credential = FIRFacebookAuthProvider.credential(withAccessToken: accessToken!)
FIRAuth.auth()?.signIn(with: credential, completion: { (user, error) in
if error != nil {
print("SIGN IN WITH FIREBASE FAILED")
completion(false)
} else {
print("YAY LOGIN SUCCESSFULL!!!!")
if let mainUser = FIRAuth.auth()?.currentUser?.providerData{
for profile in mainUser {
let providerID = profile.providerID
let uid = profile.uid // provider-specific UID
let name = profile.displayName
let email = profile.email
let photoUrl = profile.photoURL
if (FBSDKAccessToken.current() != nil) {
let facebookRequest = FBSDKGraphRequest(graphPath: "me", parameters: ["fields": "id, gender, first_name, last_name, middle_name, picture"])
facebookRequest?.start(completionHandler: { (connection, result, error) in
if error == nil {
print(result as Any)
let data = result as! NSDictionary
let gender = data.object(forKey: "gender") as! String
var newUser = User(firstName: name!, profileImageURL: ("\(photoUrl!)"), gender: gender)
newUser.save()
self.currentUserID = uid
}
})
}
completion(true)
}
}
}
})
}
}
}
That to be expected with the default login behavior. You might want to try setting loginManager.loginBehavior = .systemAccount for a smoother login experience. That will try to login with the FB credentials you gave apple in settings, which won't require switching apps at all. If the user isn't logged into FB in settings, then it'll fall back to one of the other more intrusive methods(webivew or FB app).
I have a custom button for users to register with Facebook. However when I test it is shows that I have authorized the app, but it stays on the safari page and does not return to my app. Here is what my code looks like so far.
#IBAction func facebookButtonWasHit(sender: AnyObject) {
let permissions = [ "public_profile", "email" ]
FBSDKLoginManager().logInWithReadPermissions(permissions, fromViewController: nil, handler: { (result, error) in
if error != nil {
self.presentViewController(UIAlertController(title: "Whoops!", message: error!.localizedDescription), animated: true, completion: nil)
}
else if result.isCancelled {
self.presentViewController(UIAlertController(title: "Whoops!", message: "We couldn't access facebook! Did you hit cancel?"), animated: true, completion: nil)
}
else {
if((FBSDKAccessToken.currentAccessToken()) == nil){
FBSDKGraphRequest(graphPath: "me", parameters:["fields":"email,name"]).startWithCompletionHandler({ (connection, result, error) in
if error != nil {
self.presentViewController(UIAlertController(title: "Whoops!", message: error!.localizedDescription), animated: true, completion: nil)
} else {
if let loginResult = result as? Dictionary<String,AnyObject> {
dispatch_async(dispatch_get_main_queue(), {
if let emailID = loginResult["email"] as? String{
self.emailTextField.text = emailID
}
self.nameTextField.text = loginResult["name"] as? String
let userID = loginResult["id"] as! String
let facebookProfileUrl = "http://graph.facebook.com/\(userID)/picture?type=large"
let url = NSURL(string:facebookProfileUrl)
self.picChanged = true
self.downloadImage(url!)
})
}
}
})
}
}
})
}