As much as I love Firebase, especially it’s easy to implement authentication, for some things Firebase can be a bit confusing when you go to implement them.
For Firebase Authentication, sadly, you cannot store any additional information and easily query it for authenticated users. You can leverage custom claims to add little pieces of meta to a user (like roles), but for things such as profile data, you can’t.
Fortunately, there is a solution you can easily implement using Firebase Functions and triggers.
The Workflow
A user signups for your application using Firebase Authentication. At the same time, you want to also create a new document inside of Firestore for that user and store some of their user information like uid
as well as any custom claims, display name and so on.
This will then allow you to query Firestore for any additional data for this user. It might be fields like; date of birth, country, first and last name, their likes, a bio, anything.
Firebase Triggers
Inside of Firebase Functions, you can add a trigger for onCreate
which will get called when a user creates an account in your application. This will be called when the user logs in using social oAuth (Google, GitHub, etc) as well as email/password and other login methods.
export const createUser = functions.auth.user().onCreate((user) => { const { uid, displayName, email } = user; return admin.firestore() .collection('users') .doc(uid) .set({ uid, displayName, email }) });
The code is fairly easy to understand. When a new user is created, we pull out their uid
as well as displayName
(if social oAuth login) and email
. The technique here is creating a new user, using their uid
as their unique identifier (document name).
Something to keep in mind is this trigger will only fire once. You will have to completely delete the user and force them to sign up again if you want it to trigger again.
Cleaning Up
If a user deletes their account, you also want to make sure that you use the onDelete
trigger to remove the user from your database.
export const deleteUser = functions.auth.user().onDelete((user) => { return admin.firestore() .collection('users') .doc(user.uid) .delete(); });
Defining Rules
When you are working with Firestore, it is important to properly create rules to prevent potential security issues in your application. You don’t want just anyone being able to read your users database and leaking sensitive information.
Because Firestore rules allow you to define helper methods, we are going to create two methods isAuth
and isUser
. The auth method will check if a user is logged in and the second method will allow us to pass in a uid
and compare that with the currently logged in user.
rules_version = '2'; function isAuth() { return request.auth != null && request.auth.uid != null; } function isUser(uid) { return request.auth.uid == uid; } service cloud.firestore { match /databases/{database}/documents { match /{document=**} { allow read, write: if false; } match /users/{userId} { allow read: if isUser(userId); allow create: if isAuth(); allow update: if isUser(userId); allow delete: if isUser(userId); } } }
We only want the logged in user to be able to read their own document. For creating new users, a user has to be logged in. For updates and deletes, we want to verify the logged in user matches the document.
With that, you now have a functional auto-workflow that will create new user documents in Firestore whenever someone signs up to your application and the data will be protected.
Expanding this out, you would add in additional checks to also allow admins to view and manage users as well, but that goes beyond the scope of this simple article.
Great explanation, and just what I’m looking for. Bookmarking this page for reference 😎