Swift/Firebase: How to Properly Store a Facebook User into Firebase Database When They Create an Account

Swift / Firebase: How do I properly store a Facebook user into Firebase database when they create an account?

You should only update the values when the completion block graphRequest.startWithCompletionHandler is executed because that's when you will get your data from the Facebook!.
usersReference.updateChildValues needs to be inside graphRequest.startWithCompletionHandler({ (connection, result, error) -> Void in the completion block. I have attached it below. Try it!!

func showLoginView() {
let loginManager = FBSDKLoginManager()
loginManager.logInWithReadPermissions(fbPermissions, fromViewController: self, handler: { (result:FBSDKLoginManagerLoginResult!, error:NSError!) -> Void in

if ((error) != nil) {
print("Error loggin in is \(error)")
} else if (result.isCancelled) {
print("The user cancelled loggin in")
} else {
// No error, No cancelling:
// using the FBAccessToken, we get a Firebase token
let credential = FIRFacebookAuthProvider.credentialWithAccessToken(FBSDKAccessToken.currentAccessToken().tokenString)

// using the credentials above, sign in to firebase to create a user session
FIRAuth.auth()?.signInWithCredential(credential) { (user, error) in
print("User logged in the firebase")

// adding a reference to our firebase database
let ref = FIRDatabase.database().referenceFromURL("https://project-12345.firebaseio.com/")

// guard for user id
guard let uid = user?.uid else {
return
}

// create a child reference - uid will let us wrap each users data in a unique user id for later reference
let usersReference = ref.child("users").child(uid)

// performing the Facebook graph request to get the user data that just logged in so we can assign this stuff to our Firebase database:
let graphRequest : FBSDKGraphRequest = FBSDKGraphRequest(graphPath: "me", parameters: ["fields": "id, name, email"])
graphRequest.startWithCompletionHandler({ (connection, result, error) -> Void in

if ((error) != nil) {
// Process error
print("Error: \(error)")
} else {
print("fetched user: \(result)")

// Facebook users name:
let userName:NSString = result.valueForKey("name") as! NSString
self.usersName = userName
print("User Name is: \(userName)")
print("self.usersName is \(self.usersName)")

// Facebook users email:
let userEmail:NSString = result.valueForKey("email") as! NSString
self.usersEmail = userEmail
print("User Email is: \(userEmail)")
print("self.usersEmail is \(self.usersEmail)")

// Facebook users ID:
let userID:NSString = result.valueForKey("id") as! NSString
self.usersFacebookID = userID
print("Users Facebook ID is: \(userID)")
print("self.usersFacebookID is \(self.usersFacebookID)")

//graphRequest.startWithCompletionHandler may not come back during serial
//execution so you cannot assume that you will have date by the time it gets
//to the let values = ["name":
//By putting it inside here it makes sure to update the date once it is
//returned from the completionHandler
// set values for assignment in our Firebase database
let values = ["name": self.usersName, "email": self.usersEmail, "facebookID": self.usersFacebookID]

// update our databse by using the child database reference above called usersReference
usersReference.updateChildValues(values, withCompletionBlock: { (err, ref) in
// if there's an error in saving to our firebase database
if err != nil {
print(err)
return
}
// no error, so it means we've saved the user into our firebase database successfully
print("Save the user successfully into Firebase database")
})
}
})

}
}
})
}

Swift & Firebase - How to store more user data other than email and password?

Yes, you need to use the realtime database to store your user data. So after creating it you can call the setValue().

FIRAuth.auth()!.createUserWithEmail(email, password: pwd, completion: { authData, error  in
if error == nil {
let userData = ["name": name,
"surname ": surname]
let ref = FIRDatabase.database().reference()
ref.child('users').child(authData!.uid).setValue(userData)
} else {
// handle error
}
})

Link between user and the database in firebase

This is secured through Firebase Rules. You can check out the documentation for full detailed information about the topic.

The structure you are looking for would look something like this:

{
rules: {
$uid: {
".read" = "auth.uid == $uid",
".write" = "auth.uid == $uid"
}
}
}

You have to be careful with this one because this does not apply to every database structure. This one would work, if you create a node for every user in the root of your database and specify the users authentication id as the key. A user could only access the data in the node with his Firebase Authentication id, although in that node all data, also every child node of it.

Check out the docs for more information. You can find your rules in the Firebase Console in the Database tab.

Do users need to create an account to read/write to Firebase

It is best to ask them to create an account


Although:
  • it can be a non-real email address and
  • there is anonymous auth also available

It sounds like you need the app to remember that user's particular data, so that when they return to the app, it is still their data (and not someone else's) that is being accessed.

To achieve that, we need each person's data to be stored in a different place in Firebase. Traditionally, this is by having them log in to some kind of system, most conveniently Firebase itself, and then the data stored in a branch of the database defined by their user Id.

Without logging in, you could simply ask the user for an identifier, such as "Bob" or "Carol", and then store their data under their identifier. The Firebase database would therefore have the following structure.

users/Bob/highScore: 3000
users/Bob/level: 7
users/Carol/highScore: 5050
users/Carol/level: 9

However this is not secure because there is nothing stopping Carol coming to the app and saying she is "Bob". Any such client-side activity you carry out to attempt to identify the user is not really authentication (in the opinion of Firebase) because all client-side activities can be faked relatively easily.

Firebase Authentication

The standard solution is to use Firebase to authenticate each user (see the Firebase authentication docs for this), and give your app a user Id (such as "8769dsg6f8g7698769876sdgs9") which is unique and known (by Firebase) to be correct.

Firebase security rules

You can then lock down the database using Firebase Security Rules so that only user 8769dsg6f8g7698769876sdgs9 can write to any of the users/8769dsg6f8g7698769876sdgs9/.... part of the database.

If you don't use Firebase to authenticate the user, Firebase will treat the user as unauthenticated and you will have no way to restrict each user to their own section of the database. Either you leave it wide open (to hackers etc!) or users will not be able to access their own personal data on it.

They can use a FAKE email address and password

If your concern is that they won't want to give out their real email address, you can ask them to make up any email address, e.g. mickeyMouse49857430679304@hotmail.com, and set a password. There is no obligation on your app to contact them on that email address or verify that the email address is correct.

Whenever they come back to the app, or access it on another device, they need to remember the fake email address and password.

Of course, if they lose their password, there is no way to reset it.

Anonymous Authentication, but at risk of losing access

The legendary Frank von Puffelen of Firebase, himself, has added a remark about Anonymous Authentication, in the comments below. From what I understand, this avoids them having to make up a fake email address.

I think the weakness of this is that if they lose their local web storage (e.g. if they manually wipe it, or move to another device), there is no way for them to re-access the same account, unless they have planned ahead by adding an email/pw to the anonymous account.

Notify User of New Signin with Firebase - Swift/Xcode

Super simple; have a field in your user document that stores a device name along with its status.

You app will be observing this users document and when something changes, all of the users devices will be notified of that change.

Let me set this up; here's a basic Firestore structure

users
uid_0
userName: "Jay"
devices:
device_0: offline
device_1: offline

When the app starts, it will add an observer to this users document (using the uid as the documentId)

func observeUser() {
let usersCollection = self.db.collection("users")
usersCollection.document("uid_0").addSnapshotListener { (documentSnapshot, err) in

guard let document = documentSnapshot else {
print("Error fetching document: \(err!)")
return
}

let device = document.get("devices") as! [String: String]
print(device)
}
}

Now in the Firestore closure shown above, if a users device changes status, offline to online for example, it outputs all of the devices to console. You would take whatever action is needed when the device changes status.

Keep in mind that if a NEW device is added, that event will also fire so you could present a message in the UI "A new device was added!"

So then some testing code that toggles the device_0 status from offline to online. I have a button click that does self.status = !self.status and then calls the toggleStatus function

var status = false
func toggleStatus() {
var isOnline = ""
if self.status == false {
isOnline = "online"
} else {
isOnline = "offline"

}
let userCollection = self.db.collection("users")
let thisDevice = "device_0"

let devicesDict = [
"devices":
[thisDevice: isOnline] //sets device_0 to offline or online
]

let document = usersCollection.document("uid_0").setData(devicesDict, merge: true)
}

In a nutshell, when a user authenticates with a device for the first time, it would perhaps ask for a device name, or craft one from the devices mac address or something under the hood. That device name is stored in the users document/devices with it's online status.

The device name would be stored locally as well, in user defaults for example so it's automatically sent up to Firestore upon login.

The end result here is that if any users devices change status; offline to online or vice versa, or any device is added or removed all of the devices are notified of that event.



Related Topics



Leave a reply



Submit