In this tutorial, we’re going to be exploring Firebase Cloud Functions to run code on Firebase servers.

One of the main struggles when working with role-based authentication is allowing the admin users to create or invite people to their team.

If you use the admin account to create the new user, Firebase will log out and sign-in with the new account.

To fix this, we’ll use Firebase Cloud Functions. The idea is that instead of creating the account, our admin user can store the information they want in the database.

Cloud Functions reads the database and with that information creates the new user account and stores the user’s profile in the database.

Setting up Cloud Functions

If you haven’t heard about Cloud Functions yet, the TL;DR is that they are functions that run on Firebase’s servers.

You create the function, upload it, it runs. Google takes care of auto-scaling, security, and more for you.

To get started, the first thing you need to do is to install the Firebase CLI, to do that, open your terminal and type:

npm install -g firebase-tools

Once installed, go ahead and use the CLI to log into your Firebase Console with:

firebase login

It will open a browser window for you to log in. And lastly, move to the folder where you have your project and initialize Cloud Functions with:

firebase init functions

It will ask you a few things, like selecting if you want to use TypeScript or JavaScript, choosing the project it’s going to associate in your console, etc.

Choose TypeScript, and it will give you TSLint and will help you catch errors before they happen :)

Once it’s done, it will generate a folder called functions, inside, you’ll find some configuration files like package.json and the index.ts file which is where we’ll write our functions.

Open the index.ts file and make sure this is inside:

import * as functions from 'firebase-functions';
import * as admin from 'firebase-admin';

import { UserRecord } from 'firebase-functions/lib/providers/auth';
admin.initializeApp();

NOTE: The admin initialization probably won’t be there, so add it yourself.

We’re telling our Cloud Functions that we want to use the functions SDK and the admin SDK, then we’re using the admin SDK to initialize our app.

The UserRecord object is going to be used strictly for typing purposes, to ensure our project uses appropriate types.

Now it’s time to create our function, but first, let’s go over what we need, the function needs to:

  • Read Firestore to know when we’ve added a new user.
  • Get the user’s information.
  • Create the user’s account.
  • Store the user’s data inside a User Profile collection.

With that in mind, let’s create our function and accomplish the first step, reading Firestore:

exports.createTeamMember = functions.firestore
  .document(`teamProfile/{teamId}/teamMemberList/{newUserId}`)
  .onCreate(async (snap, context) => {});

We’re using the functions.firestore package to listen to the document located at:

`teamProfile/{teamId}/teamMemberList/{newUserId}`

Notice that {teamId} and {newUserId} are wild cards, so Functions will listen on those paths dynamically, so if you add these users:

`teamProfile/ATeam/teamMemberList/user1`
`teamProfile/BTeam/teamMemberList/user2`
`teamProfile/ATeam/teamMemberList/user3`
`teamProfile/BTeam/teamMemberList/user4`

Functions will catch all of them. Now that we’re listening to the database, we’re going to extract the information we need to use:

exports.createTeamMember = functions.firestore
  .document(`teamProfile/{teamId}/teamMemberList/{newUserId}`)
  .onCreate(async (snap, context) => {
    const id: string = snap.data().id;
    const email: string = snap.data().email;
    const teamId: string = snap.data().teamId;
  });

We’re accessing the information from the event.data (This is the information we store in the database about that user.)

Once we have the information, the next step would be to create the user’s account:

exports.createTeamMember = functions.firestore
  .document(`teamProfile/{teamId}/teamMemberList/{newUserId}`)
  .onCreate(async (snap, context) => {
    const id: string = snap.data().id;
    const email: string = snap.data().email;
    const teamId: string = snap.data().teamId;

    const newUser: UserRecord = await admin.auth().createUser({
      uid: id,
      email: email,
      password: '123456789',
    });
  });

We’re using the admin SDK to create a new user, and we’re passing the uid, the email, and the password we want the user to have.

Once we have that, our next step is to create the document for this user’s profile info, for that we’re going to use the admin SDK again:

exports.createTeamMember = functions.firestore
  .document(`teamProfile/{teamId}/teamMemberList/{newUserId}`)
  .onCreate(async (snap, context) => {
    const id: string = snap.data().id;
    const email: string = snap.data().email;
    const teamId: string = snap.data().teamId;

    const newUser: UserRecord = await admin.auth().createUser({
      uid: id,
      email: email,
      password: '123456789',
    });

    await admin.firestore().doc(`userProfile/${id}`).set({
      email: email,
      id: id,
      teamId: teamId,
      teamAdmin: false,
    });

    return newUser;
  });

That last function is going into the userProfile Collection, then goes one level deeper into the {userId} Document, and stores the information there.

We’re telling Firestore the user is not an admin, the ID of the team they belong to, and the user’s personal information (email and ID).

Our function is ready to be used, so go ahead and open the terminal again inside your project’s folder and type:

firebase deploy --only functions

It will deploy your Function to Firebase, go ahead and try it, you can go to your app and use the add user function we created, make sure the function is triggering, that it creates the user, and that it creates the profile in the database.