Home
> Creating a Firestore User Document on Sign-Up
šŸ“§ Subscribe to our Newsletter for course and post updates!

Creating a Firestore User Document on Sign-Up

10 min read

Jonathan Gamble

jdgamble555 on Wednesday, April 17, 2024 (last modified on Wednesday, April 17, 2024)

When a user logs in to most Firebase apps, the first thing your app needs to do is create a user document. There are ways to do this automatically, so your app can always function without bugs.

TL;DR#

You can create a user document automatically when a user signs up by using a Trigger Function, creating a user on the server, or listing for the user login, and then creating it. If you donā€™t use a server to handle user authentication, you should check for a user document on all sign-ins and create a user document if necessary.

Creating a User Collection#

Once your app needs some features, you will inevitably need to store the user data in a users collection. This should be done on signUp and verified on signIn methods.

User Rules#

Ensure the correct Firestore Rules are in place to verify writability in the users collection. You could write this a million different ways.

	match /users/{document} {
  allow write: if requestUser() == requestDoc();
  allow read;
}

function requestDoc() {
  // 4th path is doc id
  return request.path[4];
}

function requestUser() {
  return request.auth.uid;
}

šŸš« Bad Method#

This is the method that may come to mind for some of you.

	const credential = await signInWithPopup(auth, provider);
const additionalInfo = getAdditionalUserInfo(credential);
if (additionalInfo?.isNewUser) {
    await setDoc(doc(db, 'users', user.uid), {
        displayName: user.displayName,
        photoURL: user.photoURL,
        email: user.email,
        createdAt: serverTimestamp()
    });
}

The problem with this method is that there are some edge cases where clients fail, and a user needs to be created on the first sign-in. Then your app fails when the isNewUser flag is not set. If you want to do everything on the client, you should always check for a user document (you need to get it anyway) and create one if is not there.

Client Method#

First, we need a function to create a user document. We store the name, photo, and email here in Firestore, but this could be any database. Firebase Authentication is built to work with any database you want.

	 const createUserDoc = async (user: UserType) => {

    // get user doc
    const userDoc = await getDoc(
        doc(db, 'users', user.uid)
    );

    // create one if DNE
    if (!userDoc.exists()) {
        await setDoc(doc(db, 'users', user.uid), {
            displayName: user.displayName,
            photoURL: user.photoURL,
            email: user.email,
            createdAt: serverTimestamp()
        });
    }

    // return user
    return user;
};

We can then call this function inside onIdStateChanged.

	const user = (
    defaultUser: UserType | null = null
) => readable<UserType | null>(
    defaultUser,
    (set: Subscriber<UserType | null>) => {
        return onIdTokenChanged(auth, (user: User | null) => {
            if (!user) {
                set(null);
                return;
            }
            const { displayName, photoURL, uid, email } = user;

            // create user doc if DNE
            createUserDoc({
                displayName,
                photoURL,
                uid,
                email
            }).then((_user) => {
                set(_user);
            });
        });
    }
);

šŸ“Œ Do not use await inside observables, effects, or stores. Instead, call the results inside the then function. Some implementations can have undesired effects even with async.

Other Frameworks#

You can follow the same pattern in any Framework by putting the createUserDoc function inside onIdStateChanged.

Server Trigger Method#

The Firebase Trigger Function is extremely simple. Add the user on user creation.

	import { auth } from 'firebase-functions';
import { FieldValue, getFirestore } from 'firebase-admin/firestore';

const db = getFirestore();

export const createUser = auth.user().onCreate((user) => {

    const { displayName, phoneNumber, email } = user;
    
    return db.collection('users').doc(user.uid).create({
        displayName,
        phoneNumber, 
        email,
        createdAt: FieldValue.serverTimestamp()
    });

});

Feel free to add the createdAt in any method you choose, but you can also use user.metadata.creationTime to get the creation time anywhere in your app.

ā­ Firestore Functions Generation 2 do NOT provide support for Authentication Event Triggers.

Delete Trigger#

You could also delete a user from your collection with onDelete, but I wouldnā€™t recommend using this. You would need to make sure you have cascade delete that removes all the userā€™s references in other collections (posts, tweets, comments, reviews, etc). Generally, to delete a user, you would want to soft delete or disable the user on the client side.

Server Only Method#

You could also manually create a user on the server using firebase-admin. However, you will not have access to user methods like signInWithPopup, as this would need to be done on the client. You would pass in the user information and create the user on the server. This could be a callable Firebase function or directly in your SSR Framework.

	import { getAuth } from 'firebase-admin/auth';
...
const adminAuth = getAuth();

const userData = {
    email: '[email protected]',
    emailVerified: true,
    displayName: 'Jonathan Gamble',
    photoURL: 'https://code.build/images/code.build-1x1.webp'
};

const user = await adminAuth.createUser(userData);

createUserDoc({ ...user, uid: user.uid });

Considerations#

  • You could also consider protecting your routes by checking for a user collection on the server, but you would have to be cognizant of document reads.
  • If you have the Firebase SDK present on the client, a beginner hacker could create a new user without access to your server. This could potentially create a problem with your app, as the user would not be guaranteed to have a user document unless you take precautions.
  • While the onCreate trigger method is the safest method, the user document will not be created immediately, so your app needs to account for the delay.
  • You cannot secure fields in a document, only documents themselves. A user document is great for profile information, but you should create a separate collection or subcollection for private user data.

J


Related Posts

šŸ”„ Authentication

šŸ”„ Counting Documents

šŸ”„ Data Modeling

šŸ”„ Framework Setup

šŸ”„Under Construction

Newsletter

šŸ“§ Get our course and blog post updates!

Ā© 2024 Code.Build