Adding Custom Attributes to Firebase Auth - database

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.
});

Related

How to get the user's phone number using react-google-login?

I'm actually using this React library (https://www.npmjs.com/package/react-google-login) to authenticate with Google.
As for the basic profile and email scopes, this works fine. On my client app on Google Cloud Platform, I've correctly enabled the People API (https://developers.google.com/people) and added the correct scope to the scope list, in React (https://www.googleapis.com/auth/user.phonenumbers.read). I've also ensured that my phone number on my Google Profile was made public even if I don't know if that could matter. After doing all this, the consent screen is working fine asking me to allow the app to access my phone number.
However after login, I can only see the data linked to profile and email scopes. In the library I can see that they made some object properties as shown in the code below inside the library itself :
function handleSigninSuccess(res) {
/*
offer renamed response keys to names that match use
*/
const basicProfile = res.getBasicProfile()
const authResponse = res.getAuthResponse(true)
res.googleId = basicProfile.getId()
res.tokenObj = authResponse
res.tokenId = authResponse.id_token
res.accessToken = authResponse.access_token
res.profileObj = {
googleId: basicProfile.getId(),
imageUrl: basicProfile.getImageUrl(),
email: basicProfile.getEmail(),
name: basicProfile.getName(),
givenName: basicProfile.getGivenName(),
familyName: basicProfile.getFamilyName()
}
onSuccess(res)
}
\
So the problem is that I don't know if I even receive the phone data or if I just can't read it because I don't know how to call the phone data inside the response, in terms of variables name in React. Anyone has an idea ?
The library you're using seems to be using Google Identity which does not include a method to obtain the user's phone number, just their basic profile.
You can get the user's phone number with the people api but this is not in the scope of this library. You'd have to make your own method to get the the authenticated user profile with the phoneNumbers field. Enabling the people api and requesting authorization are just the first steps.
https://developers.google.com/people/api/rest/v1/people/get

Adding extra information to registration user Firebase

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.

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.

Firebase: How to get the $id of an object after I authenticate using angularFire

Firebase has the $onAuth method that listens for changes to the client's authentication state. If the client is authenticated, the callback will be passed an object that contains the uid.
Here is my question:
Based on this uid, how can I get the the $id for this object.
This is how my data looks like in firebase:
profile {
-K2a7k9GXodDriEZrM3f {
email: "ale#aol.com"
gravatar: "https://www.gravatar.com/avatar/1d52da55055f0c8..."
uid: "3f9e0d53-4e61-472c-8222-7fc9f663969e"
name: "ale"
}
}
I am using
auth.$onAuth(function (authData) {
if (authData) {
authData.profile = //here I want the $id of the object
}
})
All I see I can do with the authData response is to get the uid of the object by using:
$firebaseObject(ref.child('profile').child(authData.uid));
This authData does not contain the $id (i.e. the -K2a7k9GXodDriEZrM3f) of the object. And that is what I am looking for in order to have the user's information available. Is there a way to achieve this?
Thanks.
If an object has a natural unique ID, you should usually store the object under that ID. Using push IDs in those cases adds no value and only complicates things.
Most developers using Firebase do as the "Storing User Data" section in the documentation illustrates: they store users by their uid. If you follow that guideline, you can find the key as authData.uid.
If you decide to stick to your current course, you can find the key for the user with:
ref.child('profile').orderByChild('uid').equalTo(authData.uid).once('child_added', function(snapshot) {
console.log(snapshot.key());
});
But you will be making it harder for the database to find your user, thus limiting the scalability of your app.

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