Adding extra information to registration user Firebase - reactjs

I would like to make an application in React Native that allows to work in two modes, parent and child. The initial stage is registration with Firebase, then after adding additional information about the role (parent / child), registering both the child and parent, and after logging in both of them, the child will share location and parent will receive it.
I would like to add additional fields such as role (parent / child) in my application in React Native + Firebase, to later create other functionalities of the application based on the role.
Registration:
firebase
.auth()
.createUserWithEmailAndPassword(email, password)
.then(userCredentials => {
return userCredentials
.user.updateProfile({
displayName: name,
})
.additionalUserInfo.profile = {
role: role,
}
})
Homescreen
const { displayName } = firebase.auth().currentUser;
const { role } = firebase.additionalUserInfo.profile;
this.setState({displayName, role});
and role returns undefined.

The properties that you can store on a user profile are defined by Firebase Authentication. You can't just add additional properties to it as you see fit. At best Firebase will simply ignore those, but likely it will also explicitly reject them (throwing an error).
If you want to store additional information about a user, you have two main options:
Store the additional information as custom claims in the user's authentication token.
Store the additional information in an external database, such as the Firestore and Realtime Database that are part of Firebase.
While storing the data as custom claims is pretty close to what you want to accomplish, you'll need to keep a few things in mind:
Custom claims are used for authorization purposes, and for that reason can only be set from a trusted environment (such as your development machine, a server you control, or Cloud Functions). So you can't simply set the role from within the app, and will need a separate process to add that claim.
After setting a custom claim on a user profile it may take up to an hour before that change is visible in the client. If you need it sooner, you can force the user to sign in again, or refresh their ID token.
Custom claims are sent with every request you make to Firebase resources, and for that reason are very limited in size. There's a maximum size of 1000 bytes for custom claims for a user. While your current role will easily fit into that, it may limit what you can add later.
If instead you store user data in an external database, you'll typically combine it with other information about that user into a users node/collection. In here you'd store a document/node for each user based on that user's UID, and then their profile information.
So something like:
users: {
uidOfAleksandra: {
username: "Aleksandra",
displayName: "Aleksandra Lastname",
role: "parent",
registrationDate: "2020-02-01"
},
uidOfPuf: {
username: "puf",
displayName: "Frank van Puffelen",
role: "child",
registrationDate: "2015-03-07"
},
}
Having this list of user profiles does not only allow you to store the additional information for each user, but would also allow you to query that list of users from within your app, something that the Authentication API doesn't allow from within application code.

Related

Create user profile with user choice on firestore using redirect

I am creating a site using react-redux-firebase, and when a user logs in with facebook I want to create a profile that stores a user choice. I know how to define what to store with the profileFactory, but I don't know how to pass dynamic data coming from the user. In my case I want to save the users' language. My configuration is similar to this:
const config = {
userProfile: 'users', // where profiles are stored in database
profileFactory: (userData, profileData) => { // how profiles are stored in database
const { user } = userData
return {
language: [i need to put here what the user chose]
email: user.email
}
}
}
The configuration was based on this recipe.
When logging in I'm using type redirect.
await firebase.login({
provider: 'facebook',
type: 'redirect'
})
How could I save the user choice when the user is created?
To solve the user custom fields I'm creating a user when the redirect comes back from facebook.
I'm still not totally sure this is the best way to do it. Seems odd that I can define a profileFactory but can't pass user custom fields in some way.

How to register and store user info in Firebase Real Time Database

I just want to know that how can i register user in Firebase and store user's additional details like First name, Last name etc i made a typical registration form in Angular I am successfully registering users with their user name and password but i want to store user's detail on success for future use. I've done this in angular 1 but could not find anyway to work with it Angular2
The best way is to create a user object in your Firebase Realtime Database using the Firebase Authentication uid from the newly created user:
this.af.auth.createUser({
// Create Firebase Auth user
email: formData.value.email,
password: formData.value.password
}).then((user) => {
// User created now create Firebase Database user
return this.af.database.object(`/users/${user.uid}`).update({
firstName: formData.value.firstName,
lastName: formData.value.lastName
});
}).then(() => {
// Success
}).catch((error) => {
// Error
console.log(error);
});
(Not had chance to test this)
Depending on how you've done registration you can obviously replace the formData.value with your model data if needed.
Also, this is done using the AngularFire2 library - https://github.com/angular/angularfire2

How to create custom URLs for new users using Firebase?

I have a React/Redux application that creates and saves new users under Firebase as /users/<uid>
However, I also want to have each user have a custom URL for their profile pages, like domain.com/users/john-david or domain.com/users/john-david-1343 (if the original /users/john-david is already taken.)
What's the best way to do this?
My problem is that I was going to store users under /users/<firstname-lastname-hash> in Firebase, but that seems like a bad idea to store them under anything besides uid (is this true?).
But also, if I don't do that, then how do I maintain /users/<uid> in Firebase, while creating new usernames that account for duplicates, and searching if the user exists when visiting domain.com/users/john-david-123
Here is some sample code of where the new user gets saved to Firebase
export function saveUser (data, user) {
let username = data.username
return ref.child(`users/${user.uid}/info`)
.set({
email: user.email,
firstname: data.firstName,
lastname: data.lastName,
admin: false,
// for /users/<username>
username: username,
uid: user.uid
})
.then(() => user)
}
In general we say you should store data in Firebase how you intend to view it, and there's no reason in particular that you must use the UID as your primary key when storing users. What you might want to is:
Store user data in /users/${username} as you mentioned, including a uid field.
When a user signs up, generate the firstname-lastname slug and store in /uids/${uid} = username.
This way you have a simple way to map from UID to user and vice-versa. The downside here is that once you bake a username into your data structure, you should probably never let them change their username.
Alternatively, you could reverse my suggestion and store /usernames/${username} = uid and /users/${uid}. This makes it easier to rename usernames, but requires that you do an extra lookup to go from username to data.

Adding Custom Attributes to Firebase Auth

I have hunted through Firebase's docs and can't seem to find a way to add custom attributes to FIRAuth. I am migrating an app from Parse-Server and I know that I could set a user's username, email, and objectId. No I see that I have the option for email, displayName, and photoURL. I want to be able to add custom attributes like the user's name. For example, I can use:
let user = FIRAuth.auth()?.currentUser
if let user = user {
let changeRequest = user.profileChangeRequest()
changeRequest.displayName = "Jane Q. User"
changeRequest.photoURL =
NSURL(string: "https://example.com/jane-q-user/profile.jpg")
changeRequest.setValue("Test1Name", forKey: "usersName")
changeRequest.commitChangesWithCompletion { error in
if error != nil {
print("\(error!.code): \(error!.localizedDescription)")
} else {
print("User's Display Name: \(user.displayName!)")
print("User's Name: \(user.valueForKey("name"))")
}
}
}
When I run the code, I get an error that "usersName" is not key value compliant. Is this not the right code to use. I can't seem to find another way.
You can't add custom attributes to Firebase Auth. Default attributes have been made available to facilitate access to user information, especially when using a provider (such as Facebook).
If you need to store more information about a user, use the Firebase realtime database. I recommend having a "Users" parent, that will hold all the User children. Also, have a userId key or an email key in order to identify the users and associate them with their respective accounts.
Hope this helps.
While in most cases you cannot add custom information to a user, there are cases where you can.
If you are creating or modifying users using the Admin SDK, you may create custom claims. These custom claims can be used within your client by accessing attributes of the claims object.
Swift code from the Firebase documentation:
user.getIDTokenResult(completion: { (result, error) in
guard let admin = result?.claims?["admin"] as? NSNumber else {
// Show regular user UI.
showRegularUI()
return
}
if admin.boolValue {
// Show admin UI.
showAdminUI()
} else {
// Show regular user UI.
showRegularUI()
}
})
Node.js code for adding the claim:
// Set admin privilege on the user corresponding to uid.
admin.auth().setCustomUserClaims(uid, {admin: true}).then(() => {
// The new custom claims will propagate to the user's ID token the
// next time a new one is issued.
});

Securing system-generated nodes in firebase

I've been going through the rules guide but haven't found an answer to this.
App users are able to submit "scores" of different types, which are then processed in JS and written to a "ranking" node. I have it set up so that every time a new score is submitted, the rankings are automatically recalculated and a new child is written if the user doesn't exist or updated if the user exists.
My question is how to secure this "ranking" node. Everyone should be able to read it, nobody except the system should be able to write it. This would prevent people from submitting their own rankings and aggregate scores.
EDIT
This is the operation:
Ref.child('rankings').child(uid).once('value', function (snapshot) {
if (snapshot.exists()) {
snapshot.ref().update(user); //user object created upstream
} else {
var payload = {};
payload[uid] = user;
snapshot.ref().parent().update(payload);
}
});
How would I add custom authentication to this call? Also, since I'm using AngularJS, is there any way to hide this custom token or would I have to route it through a backend server?
The key part of your problem definition is:
only the system should be able to write it.
This requires that you are able to recognize "the system" in your security rules. Since Firebase security is user-based, you'll have to make your "system" into a user. You can do this by either recording the uid from a regular user account or by minting a custom token for your "system".
Once you have that, the security for your ranking node becomes:
".read": true,
".write": "auth.uid == 'thesystem'"
In the above I assume you mint a custom token and specify thesystem as the uid.

Resources