Creating a Waitlist with Firebase Auth

Implement a waitlist sign-up with Firebase Auth and allow sign-ins in batches to your SaaS

If you are building a new SaaS and only want to make your service available to small batches of users, you may wish to allow signups but restrict users who can sign into your application.

Thanks to Firebase Auth Blocking Functions, this has become a much easier task than in the past. In this article, we want to show you how to create a waitlist that allows users to sign up but restricts those who can sign in in batches of users.

This post assumes you have a working authentication implementation with Firebase and Next.js.

This is the (straightforward) strategy we will use:

  1. We define a beforeCreate function: on sign-up, we add users to a collection waitlist: we will store the user ID and the timestamp when they signed up
  2. We define a max timestamp where users that signed in before the timestamp will be allowed to sign in
  3. We define a beforeSignIn function: on sign-in, we verify that the user signed up before the timestamp constant we defined

Adding users to a Waitlist Collection

When users sign-up, we add them to a collection we name waitlist: in this collection's documents, we will store the user's ID and the timestamp when they signed up.

The logic is the following:

  • allow users that signed up before timestamp to sign in and use the application. timestamp is defined as a hard-coded timestamp, but of course, you can apply logic as complex as you feel like.
src/functions/index.ts
import { auth } from 'firebase-functions';
import { getFirestore } from 'firebase-admin/firestore';
export const onSignUp = auth.user().beforeCreate(async (user) => {
const collection = await getWaitlistCollection();
await collection.add({
userId: user.uid,
timestamp: new Date().getTime(),
});
});
async function getWaitlistCollection() {
await initializeFirebaseAdminApp();
const firestore = getFirestore();
return firestore.collection(`waitlist`);
}

Initially, we may want to fully disable sign-ins:

export const onSignIn = auth.user().beforeSignIn( ({ uid }) => {
throw new auth.HttpsError('invalid-argument', `Unauthorized user`);
});

Verifying Users signed in before timestamp cutoff

When it's time to allow users to sign in, we can implement the following logic:

  1. Retrieve the user record in the waitlist
  2. Compare the max timestamp cutoff with the record's timestamp property
  3. Reject sign-in if the record's timestamp property is greater than the cutoff time
// Thu Oct 06 2022 19:51:58 GMT+0200 (Central European Summer Time)
const ALLOWED_MAX_TIMESTAMP = 1665078710248;
export const onSignIn = auth.user().beforeSignIn(async ({ uid }) => {
const collection = await getWaitlistCollection();
const docRef = await collection.where('userId', '==', uid).get();
const doc = docRef.docs[0]?.data();
if (!doc) {
throw new auth.HttpsError('invalid-argument', `Unauthorized user`);
}
if (doc.timestamp > ALLOWED_MAX_TIMESTAMP) {
throw new auth.HttpsError('invalid-argument', `Unauthorized user`);
}
});

Of course, remember to handle the errors on your client-side components.

And that's it! If you need help with the above, feel free to reach out or join our Discord Community!.