Advanced Authentication features

1. Set up

Get the source code

In this codelab, you start with a version of the Friendly Chat sample app that is nearly complete, so the first thing you need to do is clone the source code:

$ git clone https://github.com/firebase/codelab-friendlychat-web --branch security

Then, move into the security-start directory, where you will work for the remainder of this codelab:

$ cd codelab-friendlychat-web/security-start

Now, install the dependencies so you can run the code. If you're on a slower internet connection this may take a minute or two:

$ npm install && (cd functions && npm install)

Get to know this repo

The security-solution/ directory contains the complete code for the sample app. The security-start directory is where you'll work through the codelab, and is missing a few important parts of the authentication implementation. The key files and features in security-start/ and security-solution/ are:

  • functions/index.js contains Cloud Functions code, and it's where you will write auth blocking functions.
  • public/ - contains the static files for your chat app
  • public/scripts/main.js - where your chat app JS code (src/index.js) is compiled to
  • src/firebase-config.js - contains the Firebase configuration object that is used to initialize your chat app
  • src/index.js - your chat app JS code

Get the Firebase CLI

The Emulator Suite is part of the Firebase CLI (command-line interface), which can be installed on your machine with the following command:

$ npm install -g firebase-tools@latest

Build the javascript with webpack, which will create main.js inside the public/scripts/ directory.

webpack build

Next, confirm that you have the latest version of the CLI. This codelab works with version 11.14 or higher.

$ firebase --version
11.14.2

Connect to your Firebase project

If you don't have a Firebase project, in the Firebase console, create a new Firebase project. Make a note of the Project ID you choose, as you will need it later.

Now you need to connect this code to your Firebase project. First run the following command to log in to the Firebase CLI:

$ firebase login

Next run the following command to create a project alias. Replace $YOUR_PROJECT_ID with the ID of your Firebase project.

$ firebase use $YOUR_PROJECT_ID

Now you're ready to run the app!

2. Run the emulators

In this section, you'll run the app locally. This means it is time to boot up the Emulator Suite.

Start the Emulators

From inside the codelab source directory, run the following command to start the emulators:

$ firebase emulators:start

This will serve your app at http://127.0.0.1:5170 and continually rebuild your source code as you make changes. You'll only need to hard refresh (ctrl-shift-r) locally in your browser to see your changes.

You should see some output like this:

i  emulators: Starting emulators: auth, functions, firestore, hosting, storage
✔  functions: Using node@16 from host.
i  firestore: Firestore Emulator logging to firestore-debug.log
✔  firestore: Firestore Emulator UI websocket is running on 9150.
i  hosting[demo-example]: Serving hosting files from: ./public
✔  hosting[demo-example]: Local server: http://127.0.0.1:5170
i  ui: Emulator UI logging to ui-debug.log
i  functions: Watching "[...]" for Cloud Functions...
✔  functions: Loaded functions definitions from source: beforecreated.
✔  functions[us-central1-beforecreated]: providers/cloud.auth/eventTypes/user.beforeCreate function initialized (http://127.0.0.1:5011/[...]/us-central1/beforecreated).
i  Running script: npm start
 
> security@1.0.0 start
> webpack --watch --progress
[...]
webpack 5.50.0 compiled with 1 warning in 990 ms

Once you see the All emulators ready message, the app is ready to use.

3. Implementing MFA

MFA has been partially implemented in this repo. You'll add the code to first enroll a user in MFA and then to prompt users enrolled in MFA for a second factor.

In your editor, open the src/index.js file and find the startEnrollMultiFactor() method. Add the following code to set up the reCAPTCHA verifier that will prevent phone abuse (the reCAPTCHA verifier is set to invisible and won't be visible to users):

async function startEnrollMultiFactor(phoneNumber) {
  const recaptchaVerifier = new RecaptchaVerifier(
    "recaptcha",
    { size: "invisible" },
    getAuth()
  );

Then, find the finishEnrollMultiFactor() method and add the following to enroll the second factor:

// Completes MFA enrollment once a verification code is obtained.
async function finishEnrollMultiFactor(verificationCode) {
  // Ask user for the verification code. Then:
  const cred = PhoneAuthProvider.credential(verificationId, verificationCode);
  const multiFactorAssertion = PhoneMultiFactorGenerator.assertion(cred);
 
  // Complete enrollment.
  await multiFactor(getAuth().currentUser)
    .enroll(multiFactorAssertion)
    .catch(function (error) {
      alert(`Error finishing second factor enrollment. ${error}`);
      throw error;
    });
  verificationId = null;
}

Next, find the signIn function and add the following control flow that prompts users enrolled in MFA to enter their second factor:

async function signIn() {
  // Sign in Firebase using popup auth and Google as the identity provider.
  var provider = new GoogleAuthProvider();
  await signInWithPopup(getAuth(), provider)
    .then(function (userCredential) {
      // User successfully signed in and is not enrolled with a second factor.
    })
    .catch(function (error) {
      if (error.code == "auth/multi-factor-auth-required") {
        multiFactorResolver = getMultiFactorResolver(getAuth(), error);
        displaySecondFactor(multiFactorResolver.hints);
      } else {
        alert(`Error signing in user. ${error}`);
      }
    });
}

The rest of the implementation, including the functions invoked here, is already complete. To see how they work, browse through the rest of the file.

4. Try out signing in with MFA in the emulators

Now try out the MFA implementation! Make sure your emulators are still running and visit the locally-hosted app at localhost:5170. Try signing in, and when you're prompted to provide the MFA code, you'll see the MFA code in your terminal window.

Since the emulators fully support Multi-Factor Auth your development environment can be entirely self-contained.

To learn more about implementing MFA, see our reference docs.

5. Create a blocking function

Some applications are meant to be used only by a specific group of users. For those cases, you want to be able to create custom requirements for a user to sign up or sign in to your app.

That's what blocking functions provide: a way to create custom authentication requirements. They're Cloud Functions, but unlike most functions, they run synchronously when a user attempts to sign up or sign in.

To create a blocking function, open functions/index.js in your editor and find the commented out beforecreated function.

Replace it with this code that allows only users with a domain of example.com to create an account:

exports.beforecreated = beforeUserCreated((event) => {
  const user = event.data;
  // Only users of a specific domain can sign up.
  if (!user.email || !user.email.endsWith("@example.com")) {
    throw new HttpsError("invalid-argument", "Unauthorized email");
  }
});

6. Try out blocking function in the emulators

To try out the blocking function, make sure your emulators are running, and in the web app at localhost:5170, sign out.

Then, try to create an account with an email address that doesn't end in example.com. The blocking function will prevent the operation from succeeding.

Now, try again with an email address that does end in example.com. The account will be created successfully.

With blocking functions, you can create any restrictions you need around authentication. To learn more, see the reference docs.

Recap

Great job! You added Multi-Factor Authentication to a web app to help users keep their account secure, and then you created custom requirements for users to sign up using blocking functions. You've definitely earned a gif!

a gif of people from the office doing the raise the roof dance