dynamic keys of firebase custom claims in real-time database security rules - firebase

I add a dynamic json key to an user's custom claims and i want to use that key/value to allow user to write on a node.
Ex: Here is how i do it using the firebase admin sdk for nodejs.
// I get the user object
admin.auth().getUser(uid).then(userRecord => {
let claims = userRecord.customClaims; // current user claims
// I add the new key/value to the current claims object
// shop.id is obtained with DatabaseReference.push()
//Note: I prepend a 'B' to prevent a JSON key starting with '-'
claims[`B${shop.id}`] = someData[userRecord.uid] === 'manager' ? 1 : 0;
// I update the user claims
return admin.auth().setCustomUserClaims(userRecord.uid, claims);
});
In the realtime database security rules i have a node where i control the write permission with the following rule:
No other rule out of the mNode scope
{
"mNode":{
"$shopId":{
".read": "auth.uid!==null"
".write":"auth.token['B'+$shopId]===1"
}
}
}
On android i try to write on the path.
DatabaseReference ref = FirebaseDatabase.getInstance().getReference();
ref.child("mNode").child("theShopIdUsedInClaims").setValue(t)
.addOnCompleteListener(task -> {
if(task.isSuccessful()){
listener.onSuccess();
}else {
listener.onError(task.getException(),ref.toString());
}
});
The claims are successfully set to the user's token. I check them by 'stringifying' the user's claims object. But the write access is not granted.
Thank you for your help and sorry for my poor english.

Related

Is there a way to generate a firebase email verification link before a user is actually signed up?

I am currently implementing a MFA system with Firebase Authentication & Google Authenticator.
Since my users are not allowed to authenticate with a non-verified email address, I'd like to prevent them from signing-in if their Firebase Authentication email_verified is set to false. To do that, I am using Google Cloud Identity Provider blocking functions, this works perfectly.
However, when it comes to the registration beforeCreate blocking function hook, I can't find a way to generate an email verification link for the user currently being created, the documentation says:
Requiring email verification on registration The following example
shows how to require a user to verify their email after registering:
export.beforeCreate = authClient.functions().beforeCreateHandler((user, context) => {
const locale = context.locale;
if (user.email && !user.emailVerified) {
// Send custom email verification on sign-up.
return admin.auth()
.generateEmailVerificationLink(user.email)
.then((link) => {
return sendCustomVerificationEmail(
user.email, link, locale
);
});
}
});
export.beforeSignIn = authClient.functions().beforeSignInHandler((user, context) => {
if (user.email && !user.emailVerified) {
throw new gcipCloudFunctions.https.HttpsError(
'invalid-argument', `"${user.email}" needs to be verified before access is granted.`);
}
});
However, as far as I understand, generateEmailVerificationLink() can only be called to generate email verification link of an existing Firebase Authentication user. At this stage (while running beforeCreate blocking function), the user is not created yet.
Now I am wondering, I am missing something or is the Google documentation wrong?
No.
User data is created upon registration in the database.
Then, you may send an Email-Verification with a link automatically.
This Email-Verification just updates the field emaiVerified of said user data.
If you want to prevent users with unverified Emails from logging in, you need to adjust your Login page and check whether emaiVerified is true.
Important: Google will sign in a user right upon registration whether the email is verified or not, as this is the expected behavior from the perspective of a user. Email verification is ensured on the second, manual login.
(Also, please do not screenshot code.)
You can let a user sign in via email link at first, and call firebase.User.updatePassword() to set its password.
I am using Angular-Firebase, this is the logic code.
if (this.fireAuth.isSignInWithEmailLink(this.router.url)) {
const email = this.storage.get(SIGN_IN_EMAIL_KEY) as string;
this.storage.delete(SIGN_IN_EMAIL_KEY);
this.emailVerified = true;
this.accountCtrl.setValue(email);
from(this.fireAuth.signInWithEmailLink(email, this.router.url)).pipe(
catchError((error: FirebaseError) => {
const notification = this.notification;
notification.openError(notification.stripMessage(error.message));
this.emailVerified = false;
return of(null);
}),
filter((result) => !!result)
).subscribe((credential) => {
this.user = credential.user;
});
}
const notification = this.notification;
const info = form.value;
this.requesting = true;
form.control.disable();
(this.emailVerified ? from(this.user.updatePassword(info.password)) : from(this.fireAuth.signInWithEmailLink(info.account))).pipe(
catchError((error: FirebaseError) => {
switch (error.code) {
case AUTH_ERROR_CODES_MAP_DO_NOT_USE_INTERNALLY.POPUP_CLOSED_BY_USER:
break;
default:
console.log(error.code);
notification.openError(notification.stripMessage(error.message));
}
this.requesting = false;
form.control.enable();
return of(null);
}),
filter((result) => !!result)
).subscribe((result: firebase.auth.UserCredential) => {
if (this.emailVerified) {
if (result.user) {
notification.openError(`注册成功。`);
this.router.navigateByUrl(this.authService.redirectUrl || '');
} else {
notification.openError(`注册失败。`);
this.requesting = false;
form.control.enable();
}
} else {
this.storage.set(SIGN_IN_EMAIL_KEY, info.account);
}
});
Mate, if database won't create a new user using his email and password, and you send him email verification which will create his account, how the heck database will know his password? If it didn't create his account in the first step? Stop overthinking and just secure database using rules and routes in application if you don't want user to read some data while he didn't confirm email address.
It is that simple:
match /secretCollection/{docId} {
allow read, write: if isEmailVerified()
}
function isEmailVerified() {
return request.auth.token.email_verified
}
I think the blocking function documentation is wrong.
beforeCreate: "Triggers before a new user is saved to the Firebase Authentication database, and before a token is returned to your client app."
generateEmailVerificationLink: "To generate an email verification link, provide the existing user’s unverified email... The operation will resolve with the email action link. The email used must belong to an existing user."
Has anyone come up with a work around while still using blocking functions?
Using firebase rules to check for verification isn't helpful if the goal is to perform some action in the blocking function, such as setting custom claims.

Firestore token doesn't update in security rules after setting custom claim

TLDR;
Setup firestore security rules based on custom claim.
Cloud firestore user is created by phone auth.
Cloud function triggers on create user and adds a custom claim role- admin. An entry is updated in realtime database to indicate the claim update.
Listen to updates to realtime database in the client and call user.getIdToken(true); after custom claim is updated.
Able to see the added custom claim in the code.
Unable to read a document in firestore due to missing permission(custom claim).
Refresh the browser page, able to read the doc now.
I have a cloud function that adds a custom claim role - admin on user create.
exports.processSignUp = functions.auth.user().onCreate((user) => {
const customClaims = {
role: 'admin',
};
// Set custom user claims on this newly created user.
return admin.auth().setCustomUserClaims(user.uid, customClaims)
.then(() => {
// Update real-time database to notify client to force refresh.
const metadataRef = admin.database().ref("metadata/" + user.uid);
// Set the refresh time to the current UTC timestamp.
// This will be captured on the client to force a token refresh.
return metadataRef.set({refreshTime: new Date().getTime()});
})
.catch(error => {
console.log(error);
});
});
I listen to change events in realtime database to detect updates to custom claim for a user.
let callback = null;
let metadataRef = null;
firebase.auth().onAuthStateChanged(user => {
// Remove previous listener.
if (callback) {
metadataRef.off('value', callback);
}
// On user login add new listener.
if (user) {
// Check if refresh is required.
metadataRef = firebase.database().ref('metadata/' + user.uid + '/refreshTime');
callback = (snapshot) => {
// Force refresh to pick up the latest custom claims changes.
// Note this is always triggered on first call. Further optimization could be
// added to avoid the initial trigger when the token is issued and already contains
// the latest claims.
user.getIdToken(true);
};
// Subscribe new listener to changes on that node.
metadataRef.on('value', callback);
}
});
I have the following security rules on cloud firestore.
service cloud.firestore {
match /databases/{database}/documents {
match /{role}/{document=**} {
allow read: if request.auth != null &&
request.auth.token.role == role;
}
}
}
After the user is created, my cloud function triggers and adds the custom claim role = admin.
As a result of user.getIdToken(true); the token is refreshed on my client and I am able to see the set custom claim.
When I try to get a document that the user should be able to read, I get a permission denied by cloud firestore security rules.
When I refresh the browser page, I am able to read the documents in the path.
I am expecting that to be able to access the firebase doc without having to refresh the browser. Is this a possibility?
Can someone please tell me what is wrong with my approach/expectation?
It can take longer than you think for the token to be propagated. I use custom claims extensively - what I have found to work is to setup .onIdTokenChanged() to track uid & token updates, and then explicitly call .getIdTokenResult(true) to update my local token. Only after both are complete can you make customClaim secured calls to Firestore and/or RTDB.

Update document with new userId after user authenticated for the second time

I have the following use case that is working as expected:
new user arrives on website
user is given an user.uid from anonymous sign in from firebase
user creates a document having as userId reference the aforementioned user.uid
in the very same page user is invited to sign in otherwise the document will be lost
user logs in and finds in is account the document
WIN!
Now I have a use case that is not working as expected:
returning user with session expired or from a different browser arrives on website
user is given an user.uid from anonymous sign in from firebase
user creates a document having as userId reference the aforementioned user.uid
in the very same page user is invited to sign in otherwise the document will be lost
user logs in and doens't find in is account the document
DIDN'T WIN THIS TIME :(
I configured firebase auth with the following configuration:
const uiConfig = {
signInFlow: 'popup',
autoUpgradeAnonymousUsers: true,
signInOptions: [
firebase.auth.GoogleAuthProvider.PROVIDER_ID,
],
callbacks: {
signInFailure: (error: any) => {
if (error.code != 'firebaseui/anonymous-upgrade-merge-conflict') {
return Promise.resolve();
}
var cred = error.credential;
return firebase.auth().SignInAndRetrieveDataWithCredential(cred);
}
},
};
So, as you can see, the issue is that, the first time, autoUpgradeAnonymousUsers create a new userId with the anonymous user id and everything is fine, but of course the second time doesn't work anymore.
How should I solve this problem given that in my security rules I want to create a check that userId cannot be updated AND only a request with the same userId can see the document?
security rules:
allow create: if request.auth.uid != null
allow read: if request.auth.uid == resource.data.userId
&& request.auth.uid != null
allow update: if request.auth.uid == request.resource.data.userId && resource.data.userId == request.resource.data.userId && request.auth.uid != null
Thank you.
The problem is you can not create a new user with the same credentials. If the user logs in he looses the data from the anonymous sign in.
You have to save the data from the anonymous user locally and after the user logs in you have to copy the data to the current user. You should also deleting the anonymous account.
I have found this example which uses the Firebase realtime database to save the user data.
https://github.com/firebase/firebaseui-web#upgrading-anonymous-users
// signInFailure callback must be provided to handle merge conflicts which
// occur when an existing credential is linked to an anonymous user.
signInFailure: function(error) {
// For merge conflicts, the error.code will be
// 'firebaseui/anonymous-upgrade-merge-conflict'.
if (error.code != 'firebaseui/anonymous-upgrade-merge-conflict') {
return Promise.resolve();
}
// The credential the user tried to sign in with.
var cred = error.credential;
// If using Firebase Realtime Database. The anonymous user data has to be
// copied to the non-anonymous user.
var app = firebase.app();
// Save anonymous user data first.
return app.database().ref('users/' + firebase.auth().currentUser.uid)
.once('value')
.then(function(snapshot) {
data = snapshot.val();
// This will trigger onAuthStateChanged listener which
// could trigger a redirect to another page.
// Ensure the upgrade flow is not interrupted by that callback
// and that this is given enough time to complete before
// redirection.
return firebase.auth().signInWithCredential(cred);
})
.then(function(user) {
// Original Anonymous Auth instance now has the new user.
return app.database().ref('users/' + user.uid).set(data);
})
.then(function() {
// Delete anonymnous user.
return anonymousUser.delete();
}).then(function() {
// Clear data in case a new user signs in, and the state change
// triggers.
data = null;
// FirebaseUI will reset and the UI cleared when this promise
// resolves.
// signInSuccessWithAuthResult will not run. Successful sign-in
// logic has to be run explicitly.
window.location.assign('<url-to-redirect-to-on-success>');
});
}

How to force logout firebase auth user from app remotely

I have a project which uses firebase auth with firebaseUI to authenticate users. I have enabled Google, Facebook and email providers. What I need is to remotely logout or disable some of the users.
I want the users to logout from the app on doing so. I tried disabling the user in the firebase console and also used the firebase admin SDK (https://firebase.google.com/docs/auth/admin/manage-sessions) to revoke the refresh tokens.
I waited for more than 2 days and still noticed that the user was logged in and could access the firestore data.
I have also gone through and tried
Firebase still retrieving authData after deletion
Can anyone point to what I am doing wrong ?
You also cannot remotely force a user to be signed out. Any sign out will have to happen from the device that the user is signed in on.
There is no way to revoke an access token once that is minted. This means that even if you disable the user's account, they may continue to have access for up to an hour.
If that is too long, the trick (as also mentioned in my answer to the question you linked) is to maintain a list of blocked users in your database (or elsewhere) and then check against that in your security rules (or other authorization layer).
For example in the realtime database, you could create a list of blocked user's UIDs:
banned_uids: {
"uid1": true
"uid2": true
}
And then check against that in your security rules with:
".read": "auth.uid !== null && !root.child('banned_uids').child(auth.uid).exists()"
You can send a message data with FCM to force to log out.
For example, if the users use android application.
Save the FCM token in a collection in firebase Realtime.
configure the Android client app, in the service. LINK You have to make when receive a message with especial string, force to log out.
make the trigger you need in cloud functions, to send the data LINK when you need the user log out.
SUCCESS!
As per your scenarios, i assume that you need to make user logout when user is disabled.
Use One global variable to store TokenNo (might be in shared preference or sqlite):
Add following code to your manifest:
<service android:name=".YourFirebaseMessagingService">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service>
Add following code in your
public class LogoutOntokenchange extends FirebaseMessagingService{
#Override
public void onNewToken (String token){
if(TokenNo=>1){ //if tokenNo >=1 means he already logged in
TokenNo=0;
FirebaseAuth.getInstance().signOut(); //Then call signout method
}
else{
TokenNo=1; //store token no in db
}
}
}
What Happens here:
When user logged in first time onNewToken is called then It goes into else then TokenNo is updated to 1 from 0.
When You disable any user then automatically token is refreshed.Then OnNewToken is called then TokenNo>=1 so user will be logged out.
NOTE: When user log in for first time i.e if TokenNo variable is not stored then store it as 0.
For reference: https://firebase.google.com/docs/reference/android/com/google/firebase/messaging/FirebaseMessagingService
The only way I can think about is adding a if-else block in your starting activity.
Store the that status of user (verified/banned/deleted) in Firebase Real-time database. Then retrieve the status of user at start of application and add the code:
if (currentUserStatus.equals("banned"))
{
currentUser.logout();
}
What I've done is I created for each user upon registration a Firestore document with the UID as document ID. In this document I store an array which stores all fcm tokens the individual user receives when logging into a new device. That way I always keep track where the user is logged in. When the user logs out manually the fcm token will be deleted from the document in Firestore as well as on the device.
In order to be able to log out the user everywhere they are signed in I did the following. When starting the app and once the user is logged in I start a snapshot listener that listens to all changes in the users document. As soon as there is a change I retrieve the new array of fcm tokens, search inside the array for the local current device fcm token. If found, I do nothing. If the fcm token is no longer in the array I will call the local logout method and go back to the login screen.
Here are the methods I used in swift on iOS. The closures (passOnMethod) will just trigger an unwind segue to the login view controller.
import Foundation
import Firebase
class FB_Auth_Methods {
let db = Firestore.firestore()
var listener: ListenerRegistration?
func trackLoginStatus(passOnMethod: #escaping () -> () ) {
listener?.remove()
if let loggedInUserA_UID = Auth.auth().currentUser?.uid {
listener = db.collection(K.FStore.collectionOf_RegisteredUsers_Name)
.document(loggedInUserA_UID)
.addSnapshotListener { (snapshotDocument, error) in
if let error = error {
print(error)
} else {
if let document = snapshotDocument {
if let data = document.data() {
if let fcmTokens = data[K.FStore.Users.fcmTokens] as? [String] {
print("Found the following tokens: \(fcmTokens)")
self.compareTokensAgainstCurrentDeviceToken(fcmTokens: fcmTokens, passOnMethod: { () in
passOnMethod()
})
}
}
}
}
}
}
}
func compareTokensAgainstCurrentDeviceToken(fcmTokens: [String], passOnMethod: #escaping () -> () ) {
InstanceID.instanceID().instanceID { (result, error) in
if let error = error {
print(error)
} else if let result = result {
if fcmTokens.contains(result.token) {
print("Token found, doing nothing")
} else {
print("Token no longer found, logout user")
do {
try Auth.auth().signOut()
InstanceID.instanceID().deleteID { error in
if let error = error {
print(error)
} else {
passOnMethod()
}
}
} catch let signOutError as NSError {
print (signOutError)
}
}
}
}
}
}
And here is the method I use when logging out the user everywhere but at the current device.
func deleteAllFcmTokensExceptCurrent(loggedInUserA: User, passOnMethod: #escaping () -> () ) {
InstanceID.instanceID().instanceID { (result, error) in
if let error = error {
print(error)
} else if let result = result {
let batch = self.db.batch()
let deleteAllFcmRef = self.db.collection(K.FStore.collectionOf_RegisteredUsers_Name).document(loggedInUserA.uid)
batch.updateData([K.FStore.Users.fcmTokens: FieldValue.delete()], forDocument: deleteAllFcmRef)
let updateFcmTokenRef = self.db.collection(K.FStore.collectionOf_RegisteredUsers_Name).document(loggedInUserA.uid)
batch.updateData([K.FStore.Users.fcmTokens: FieldValue.arrayUnion([result.token])], forDocument: updateFcmTokenRef)
batch.commit { (error) in
if let error = error {
print(error)
} else {
passOnMethod()
}
}
}
}
}
Not tested yet, as our backend programmer, who is in charge of setting up Firestore rules was gone for the day, but in theory this should work: (and it's something I'll test tomorrow)
Having a FirebaseAuth.AuthStateListener in charge of serving UI based on the status of the user
This combined with rules in firestore
match /collection
allow read: if isAuth();
Where isAuth is:
function isAuth() {
return request.auth.uid != null;
}
If the user is then disabled, while being logged in, whenever the user tries to read data from the collection, he should be denied, and a signOut() call should be made.
The AuthStateListener will then detect it, and sign the user out.

How to fetch a list of 'FirebaseUser' programatically? [duplicate]

I'm working on a firebase+angularjs app and I'm using the simple email and password authentication and it's working properly.
I'm just wondering if I can add extra user data on the user table which is being used by firebase email+password auth, like I want to add billing info and other details concerning the user without creating extra node/table on firebase to store these extra data.
Firebase stores the email/password users in a separate location, that you don't have direct access to. You cannot expand the data in this location.
Since many application developers want to access the user data in their application code, it is a common practice to store all users under a /users node inside the application database itself. The disadvantage is that you have to do this yourself. But the positive side of this is that you can store any extra information if you want.
See the Firebase guide on storing user data for sample code. From there:
var ref = new Firebase("https://<YOUR-FIREBASE-APP>.firebaseio.com");
ref.onAuth(function(authData) {
if (authData && isNewUser) {
// save the user's profile into Firebase so we can list users,
// use them in Security and Firebase Rules, and show profiles
ref.child("users").child(authData.uid).set({
provider: authData.provider,
name: getName(authData)
});
}
});
NOTE: This method only works if you are using Firebase Admin SDK and you need to have end point on your server to manage custom tokens
Firebase Admin SDK has an option to create custom tokens with additional claims object, which can contain arbitrary data. This might be useful to store some user related info, like whether the user is premium user or not.
Additional claims data is accessible using auth object.
example
var uid = "some-uid"; //this can be existing user UID
var additionalClaims = {
premiumAccount: true,
some-user-property: 'some-value'
};
admin.auth().createCustomToken(uid, additionalClaims)
.then(function(customToken) {
// Send token back to client
})
.catch(function(error) {
console.log("Error creating custom token:", error);
});
additionalClaims are also accessible in Firebase security rules.
for more info read Firebase Custom Tokens
A Firebase User has a fixed set of basic properties—a unique ID, a primary email address, a name and a photo URL—stored in the project's user database, that can be updated by the user (iOS, Android, web). You cannot add other properties to the Firebase User object directly; instead, you can store the additional properties in your Firebase Realtime Database.
Firebase has a fixed set of user properties which can be updated but not added on to.
However you can add small amounts of data with the help of serialization and deserialization using JSON.stringify() and JSON.parse()
And then use any one of the unused properties to store the string
either in DisplayName, or photoURL property.
Keep in mind the data that can be added has to be small in size and stored as a string.
And this can be only possible with using the method in the FIREBASE SDK and not the angularfire as illustrated below
var user = firebase.auth().currentUser;
user.updateProfile({
displayName: "Jane Q. User",
photoURL: "https://example.com/jane-q-user/profile.jpg"
}).then(function() {
// Update successful.
}, function(error) {
// An error happened.
});
You could store more json like data in the photoURL or displaYName variable in the form of string here.
My answer is not angular related but I searched quiet a bit to find out how to do it using Polymer and Polymerfire so I add this answer to help people get it done faster than i did.
I had to add a separate node to db as Frank van Puffelen mentioned.
Imports:
<link rel="import" href="../bower_components/polymerfire/firebase-app.html">
<link rel="import" href="../bower_components/polymerfire/firebase-auth.html">
<link rel="import" href="../bower_components/polymerfire/firebase-document.html">
Then place anywhere in your app a <firebase-app> component:
<firebase-app
name="yourAppName"
api-key= "{{yourApi}}"
auth-domain= "{{yourAuthDomain}}"
database-url= "{{yourDbUrl}}"
>
</firebase-app>
After that you will need to use <firebase-auth> and <firebase-document>:
Template :
<firebase-auth
id="auth"
app-name="yourAppName"
signed-in="{{signedIn}}"
user="{{user}}">
</firebase-auth>
<firebase-document
id="document"
app-name="yourAppName"
path="{{usersPath}}" // e.g "/users"
data="{{userDocument}}">
</firebase-document>
Script:
this._register = function(){
var formValid = this.querySelector('#register-form').validate();
var auth = this.querySelector('#auth');
if(formValid && this.passWordsIdentic){
//The actual registration
auth.createUserWithEmailAndPassword(this.email, this.password).then(function(user){
console.log('auth user registration succes');
//Example values
this.userDocument.uid = user.uid;
this.userDocument.email = user.email;
this.userDocument.firstName = this.firstName;
this.userDocument.lastName = this.lastName;
this.userDocument.userName = this.userName;
this.$.document.save(this.usersPath).then(() => {
console.log("custom user registration succes");
this.$.document.reset();
});
}.bind(this)).catch(function(error) {
var errorCode = error.code;
var errorMessage = error.message;
console.log('error: ', errorCode);
);
}
}
And that's it, you may want to take a look at this excellent google codelab which is a good introduction into using firebase with polymer.
Here is the code of registration where add the extra fields in the Users table
import { AngularFireAuth } from "#angular/fire/auth";
constructor(private firebaseAuth: AngularFireAuth){}
registration(data: any, password: any) {
return this.firebaseAuth.auth.createUserWithEmailAndPassword(data.Email, password)
.then(res => {
res.user.updateProfile({
displayName: `${data.DisplayName}`
})
data.UserId = res.user.uid;
data.PhoneNumbers = [{
NumberType: '',
NumberValue: ''
}];
data.PhotoUrl = '';
data.Addresses = [{
AddressLine1: '',
AddressLine2: '',
City: '',
State: '',
Country: '',
PostalCode: '',
AddressType: ''
}];
data.IsDeleted = false;
this.fireStore.doc(`users/${res.user.uid}`).set(data);
this.toastr.success('User has been register successfully!', 'Successfull!');
return true;
}).catch(err => {
switch (err.code) {
case 'auth/email-already-in-use':
this.toastr.error(`Email address ${data.Email} already in use.`, 'Error!');
break;
case 'auth/invalid-email':
this.toastr.error(`Email address ${data.Email} is invalid.`, 'Error!');
break;
case 'auth/operation-not-allowed':
this.toastr.error('Error during sign up.', 'Error!');
break;
case 'auth/weak-password':
this.toastr.error('Password is not strong enough. Add additional characters including special characters and numbers.', 'Error!');
break;
default:
this.toastr.error(err.message, 'Error!');
break;
}
});
}
Here's a swift version. Your user structure ("table") is like
--users:
-------abc,d#email,com:
---------------email:abc.d#email.com
---------------name: userName
etc.
After you pass the auth FIRAuth.auth()?.createUser you can set the users in database as below:
let ref = FIRDatabase.database().reference()
let rootChild = ref.child("users")
let changedEmailChild = u.email?.lowercased().replacingOccurrences(of: ".", with: ",", options: .literal, range: nil) // Email doesn't support "," firebase doesn't support "."
let userChild = rootChild.child(changedEmailChild!)
userChild.child("email").setValue(u.email)
userChild.child("name").setValue(signup.name)
Please note that method is changed in v4.0.0. Therefore, you need to use the below code to retrieve the user profile:
afAuth.authState.subscribe((user: firebase.User) => {
this.displayName = user.displayName;
this.email = user.email;
this.photoURL = user.photoURL;
});
The answer from Frank is good, but things are a little different in Angular6/Firebase5/Angularfire5:
Here is my click handler for signing in a user:
this.afAuth.auth.signInWithPopup(new firebase.auth.GoogleAuthProvider()).then((e) => {
console.log("Log-In Success" + e.additionalUserInfo.profile.name);
if (e.additionalUserInfo.isNewUser)
this.addUserToDatabase(/*...*/);
}).catch((error) => {
console.log("Log-In Error: Google Sign-In failed");
});

Resources