Build a React App with Firebase Serverless Functions

Build a React App with Firebase Serverless Functions

Firebase is an exciting cloud platform from Google available to businesses today. Firebase connects everything from simple static websites to IoT devices to AI and machine learning platforms. The platform provides various services to facilitate these connections, like storage and authentication.

In this tutorial, you will learn about two core Firebase products: Cloud Functions for Firebase and Firebase Hosting. Hosting is for deploying static web applications. Functions are the Firebase serverless platform. You will create a static application using React that authenticates users via Okta’s React library. After obtaining an access token from Okta’s authorization server, you will use a Firebase function to exchange the Okta token for a Firebase token and sign in the user using Firebase’s authentication structure. You will obtain additional user information by calling the userInfo endpoint on your Okta authorization server and including that data in your Firebase token. Finally, you will create another function endpoint to handle a simple HTTP request that requires an authenticated user.

Once you’ve built your application, you will deploy it to the Firebase platform for public consumption using the Firebase CLI.

Prerequisites

  • Node.js
  • Firebase Account
  • Okta CLI

    Okta offers Authentication and User Management APIs that reduce development time with instant-on, scalable user infrastructure. Okta’s intuitive API and expert support make it easy for developers to authenticate, manage, and secure users and roles in any application.

  • Visual Studio Code
  • Firebase CLI

If you wish, you can follow along with the GitHub repository found here.

Table of Contents

Add authentication using OIDC

Before you begin, you’ll need a free Okta developer account. Install the Okta CLI and run okta register to sign up for a new account. If you already have an account, run okta login. Then, run okta apps create. Select the default app name, or change it as you see fit. Choose Single-Page App and press Enter.

Use http://localhost:3000/login/callback for the Redirect URI and accept the default Logout Redirect URI of http://localhost:3000/login.

What does the Okta CLI do?

The Okta CLI will create an OIDC Single-Page App in your Okta Org. It will add the redirect URIs you specified and grant access to the Everyone group. It will also add a trusted origin for http://localhost:3000/login. You will see output like the following when it’s finished:

Okta application configuration:
Issuer:    https://dev-133337.okta.com/oauth2/default
Client ID: 0oab8eb55Kb9jdMIr5d6

NOTE: You can also use the Okta Admin Console to create your app. See Create a React App for more information.

The CLI outputs the Issuer and Client ID. You’ll need those coming up.

Create your Firebase project

Next, open the Firebase console and click Add Project. Give your project a name, preferably okta-firebase. For now, you can turn off the analytics and create the project. Once that’s completed, you’ll be able to access the project dashboard.

First, click the Upgrade button next to the Spark option at the bottom of your screen. Change your plan to Blaze and if you wish, set a billing budget for $1 to let you know when you are incurring a charge. Blaze is pay-as-you-go, and the rates are pretty generous for hobby projects. You shouldn’t incur any charges at this time, but the budget will let you know if you do.

Click the settings wheel next to Project Overview and click Project settings. At the bottom of the overview page, there’s a prompt to Select a platform to get started and select web app (it will look like </>). Give your app the nickname okta-firebase-demo and select Also set up Firebase Hosting for this app. Click Register app, and in a moment you will see some JavaScript code for setting up your Firebase app.

Firebase configuration generated by Firebase console under 'Add Firebase SDK' step

Hold onto this information as you will need it in your app. Click through the rest of the wizard and return to your console. Again go to the Project settings section and navigate to the Service accounts tab. Click Generate new private key and let that file download. You will need this in your Firebase Function shortly.

Screenshot of 'Firebase Admin SDK' view with a button to 'Generate new private key'

Finally, click the Build>Authentication in the side nav and click Get Started. Pressing the Get Started button generates a Web API Key that is required for using the auth features.

Create your front end in React

Next, create your React app.

npx create-react-app@5 okta-firebase
cd okta-firebase

The npx command will scaffold a new React version 18 application for you.

Next, you will need to install a few dependencies to help you.

npm i @okta/okta-react@6.4.3
npm i @okta/okta-auth-js@6.2.0
npm i react-router-dom@5.3.0
npm i bootstrap@5.1.3
npm i firebase@9.8.1

First, you want to install the @okta/okta-react package to assist in authenticating users and obtaining the tokens from the Okta authorization server. This package will help you access the authentication state, direct users to the Okta login page, and handle any callbacks.

@okta/okta-react relies on the @okta/okta-auth-js package, so you need to install it.

Next, you want to install react-router-dom. This package will set up the callback route for you and any other routes you may need.

Finally, you will use the firebase package to call the various platform features in Firebase, such as functions and authentication.

Add a file called .env in your root directory and replace the code with the following.

REACT_APP_OKTA_ISSUER=https://{yourOktaDomain}/oauth2/default
REACT_APP_OKTA_CLIENTID={yourClientID}
REACT_APP_FIREBASE_APIKEY={yourFirebaseAPIKey}
REACT_APP_FIREBASE_AUTHDOMAIN={yourFirebaseAuthDomain}
REACT_APP_FIREBASE_PROJECTID={yourFirebaseProjectID}
REACT_APP_FIREBASE_STORAGEBUCKET={yourFirebaseStorageBucket}
REACT_APP_FIREBASE_MESSAGINGSENDERID={yourFirebaseMessagingSenderID}
REACT_APP_FIREBASE_APPID={yourFirebaseAppID}
REACT_APP_ENV=production

You obtained the Okta values when you created your application using the Okta CLI earlier. Your Okta domain is part of the Issuer. The Firebase values came from the configuration you copied when you first created your application.

There is currently a known error in React 18 with the Okta React library where multiple rerenders can lead to an error message in the oktaAuth object. Work on fixing this error is ongoing. In the meantime, you can work around it by taking React out of strict mode. Replace the code in your index.js file with the following code.

import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
    <App />
);

// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

Next, open your App.js file and replace the code with the following.

import "./App.css";

import { BrowserRouter as Router } from "react-router-dom";
import AppWithRouterAccess from "./AppWithRouterAccess";
import 'bootstrap/dist/css/bootstrap.min.css';


function App() {

  return (
    <Router>      
      <AppWithRouterAccess />
    </Router>
  );
}

export default App;

You are replacing the default code with a Router and an AppWithRouterAccess that you will write next. Open a new file called AppWithRouterAccess.jsx and add the following code.

import "./App.css";

import { Route, useHistory } from "react-router-dom";
import { OktaAuth, toRelativeUrl } from "@okta/okta-auth-js";
import { Security, LoginCallback } from "@okta/okta-react";

import Home from "./Home";

const {
  REACT_APP_OKTA_ISSUER,
  REACT_APP_OKTA_CLIENTID
} = process.env;

const oktaAuth = new OktaAuth({
  issuer: REACT_APP_OKTA_ISSUER,
  clientId: REACT_APP_OKTA_CLIENTID,
  redirectUri: window.location.origin + "/login/callback",
  scopes: ['openid', 'profile', 'email']
});

function AppWithRouterAccess() {
  const history = useHistory();

  const restoreOriginalUri = async (_oktaAuth, originalUri) => {
    history.replace(toRelativeUrl(originalUri || "/", window.location.origin));
  };

  return (
    <Security oktaAuth={oktaAuth} restoreOriginalUri={restoreOriginalUri}>
      <Route path="/" component={Home} />
      <Route path="/login/callback" component={LoginCallback} />
    </Security>
  );
}

export default AppWithRouterAccess;

This file will define your routes and establish the /login/callback route for Okta to handle signing in your users.

Finally, add the Home.jsx file to your application with the following code.

import React, { useState } from "react";

import { useOktaAuth } from "@okta/okta-react";

import { initializeApp } from "firebase/app";
import { getAuth, signInWithCustomToken, signOut } from "firebase/auth";
import {
  getFunctions,
  httpsCallable,
  connectFunctionsEmulator,
} from "firebase/functions";

function Home() {
  const [reportCardData, setReportCardData] = useState();
  
  const [selectedSemester, setSelectedSemester] = useState("Spring 2022");
  const { oktaAuth, authState } = useOktaAuth();

  const login = async () => oktaAuth.signInWithRedirect();
  const logout = async () => {
    signOut(auth);
    oktaAuth.signOut("/");
  };

  const {
    REACT_APP_FIREBASE_APIKEY,
    REACT_APP_FIREBASE_AUTHDOMAIN,
    REACT_APP_FIREBASE_PROJECTID,
    REACT_APP_FIREBASE_STORAGEBUCKET,
    REACT_APP_FIREBASE_MESSAGINGSENDERID,
    REACT_APP_FIREBASE_APPID,
    REACT_APP_ENV,
  } = process.env;

  const firebaseConfig = {
    apiKey: REACT_APP_FIREBASE_APIKEY,
    authDomain: REACT_APP_FIREBASE_AUTHDOMAIN,
    projectId: REACT_APP_FIREBASE_PROJECTID,
    storageBucket: REACT_APP_FIREBASE_STORAGEBUCKET,
    messagingSenderId: REACT_APP_FIREBASE_MESSAGINGSENDERID,
    appId: REACT_APP_FIREBASE_APPID,
  };

  const app = initializeApp(firebaseConfig);
  const functions = getFunctions(app);

  const auth = getAuth();

  if (REACT_APP_ENV === "development") {
    connectFunctionsEmulator(functions, "localhost", 5001);
  }

  const getGrades = async () => {
    const getGradesCall = httpsCallable(functions, "getGrades");
    const resp = await getGradesCall({
      name: selectedSemester.split(" ")[0],
      year: selectedSemester.split(" ")[1],
    });

    setReportCardData(resp.data);
  };

  const exchangeOktaTokenForFirebaseToken = async () => {
    const exchangeToken = httpsCallable(
      functions,
      "exchangeOktaTokenForFirebaseToken"
    );

    const resp = await exchangeToken({
      accessToken: authState.accessToken.accessToken
    });

    await signInWithCustomToken(auth, resp.data.firebaseToken);
  };

  if (authState?.isAuthenticated) {
    exchangeOktaTokenForFirebaseToken();
  }

  return (
    <div className="App">
      <main role="main" className="inner cover container">
        <nav className="navbar navbar-expand-lg navbar-light bg-light ">
          <ul className="nav navbar-nav ml-auto navbar-right ms-auto">
            <li>
              {auth?.currentUser && (
                <button
                  className="btn btn-outline-secondary my-2 my-sm-0"
                  onClick={logout}
                >
                  Logout
                </button>
              )}

              {!auth?.currentUser && (
                <button className="btn btn-outline-secondary" onClick={login}>
                  Login
                </button>
              )}
            </li>
          </ul>
        </nav>

        {!auth?.currentUser && (
          <div>
            <p className="lead">
              In order to use this application you must be logged into your Okta
              account
            </p>
            <p className="lead">
              <button className="btn btn-primary" onClick={login}>
                Login
              </button>
            </p>
          </div>
        )}
        {auth?.currentUser && (
          <div>
            <h1 className="cover-heading">
              Please select a semester to get your report card
            </h1>

            <div className="row">
              <div className="col-2">
                <select
                  className="form-select"
                  value={selectedSemester}
                  onChange={(e) => {
                    setSelectedSemester(e.target.value);
                  }}
                >
                  <option value="Fall 2021">Fall 2021</option>
                  <option value="Spring 2021">Spring 2021</option>
                  <option value="Fall 2022">Fall 2022</option>
                  <option value="Spring 2022">Spring 2022</option>
                </select>
              </div>
              <div className="col-2">
                <button className="btn btn-primary" onClick={getGrades}>
                  Get Grades
                </button>
              </div>
            </div>

            {reportCardData && (
              <>
                <p>
                  <b>Name: </b> {reportCardData.name}
                </p>
                <p>
                  <b>School: </b> {reportCardData.school}
                </p>
                <p>
                  <b>Semester: </b> {reportCardData.semester} -{" "}
                  {reportCardData.year}
                </p>

                <table className="table table-striped">
                  <thead>
                    <tr>
                      <th className="text-start"> Course </th>
                      <th> Score </th>
                      <th> Letter Grade </th>
                    </tr>
                  </thead>
                  <tbody>
                    {reportCardData.grades.map((grade, i) => {
                      return (
                        <tr key={i}>
                          <td className="text-start">{grade.course}</td>
                          <td>{grade.score}</td>
                          <td>{grade.letterGrade}</td>
                        </tr>
                      );
                    })}
                  </tbody>
                </table>
              </>
            )}
          </div>
        )}

        <footer
          className="bg-light text-center fixed-bottom"
          style={{
            width: "100%",
            padding: "0 15px",
          }}
        >
          <p>
            A Small demo using <a href="https://developer.okta.com/">Okta</a> to
            Secure an{" "}
            <a href="https://firebase.google.com/">
              Firebase hosted application{" "}
            </a>{" "}
            with a serverless{" "}
            <a href="https://firebase.google.com/docs/functions">function</a>
          </p>
          <p>
            By <a href="https://github.com/nickolasfisher">Nik Fisher</a>
          </p>
        </footer>
      </main>
    </div>
  );
}

export default Home;

This page will handle both the authenticated and unauthenticated states. If the user is not authenticated, they are presented with a screen requesting them to do so. If the user is authenticated, they can getGrades by selecting a semester from the drop-down and calling the server.

The getGrades function ensures the user is authenticated using the build-in authentication function in Firebase. Firebase also integrates with authentication tokens from providers such as Okta. To utilize this functionality, you will mint a Firebase authentication token using an Okta authentication token. Okta returns an accessToken to the client when the user logs in. The client then passes the accessToken to a Firebase function called exchangeOktaTokenForFirebaseToken. In this Cloud Function for Firebase, you verify the token and return a Firebase token to log the user in. Then subsequent calls to the functions will treat that user as logged in to Firebase.

At this point, you can use the npm run start command to run your app locally. You’ll see a few console errors from Firebase, and you’ll see Login buttons. Notice that you’re able to authenticate with Okta now, but the login process doesn’t call Firebase yet, so your login is still incomplete.

Make your React application ready for Firebase

Now you are ready to prepare your application for Firebase. If you haven’t done so yet, please install the Firebase CLI.

npm install -g firebase-tools@11.1.0

To log in with your Google account, you may need to run firebase login.

Next, run the command firebase init to start the initialization of your project.

Select both of the following features:

  • Functions: Configure a Cloud Functions directory and its files
  • Hosting: Configure files for Firebase Hosting and (optionally) set up GitHub Action deploys

Select Use an existing project and select your okta-firebase-{ID} project

After a moment, you’ll see prompts to set up Firebase functions. Select the following options:

  1. Language - Javascript
  2. EsLint - No (You should use this in production-ready applications.)
  3. When prompted to install dependencies say Yes

Next, select the following options to set up hosting.

  1. What do you want to use as your public directory? - build
  2. Configure as a single-page app? yes
  3. Setup automatic builds? no

Before deploying your application, you must run the build command on your React app to prepare it properly. By configuring your app as a SPA, you tell the Firebase CLI to edit the configuration to redirect to /index.html.

Add authenticated Firebase functions

You should notice that a new folder called functions has been added to your directory. There, you will see some Firebase config stuff and a file called index.js. You’re going to add the code for two functions.

First, you will need one that accepts an Okta token and returns a Firebase token for the client to use. To verify the token, you will use the @okta/jwt-verifier package from Okta.

The second function will accept arguments from the client, namely the semester, and use that along with some information from the token to build report card data for the client to use to create the report card.

Start by navigating to your functions directory and installing your dependencies.

cd functions
npm i @okta/jwt-verifier@2.3.0

The @okta/jwt-verifier will verify your JWT from Okta when calling the exchangeOktaTokenForFirebaseToken function.

Next, copy the keys file you downloaded from the Firebase console earlier and add it to your functions folder. Make a note of the name, as you will need that shortly.

Add a file to your functions folder called grades.js and add the following code.

const getGrades = (user, semester) => {
  return {
    name: user.name,
    school: getFakeUniversityName(user.email),
    semester: semester.name,
    year: semester.year,
    grades: grades
      .filter((r) => r.year == semester.year)
      .filter((r) => r.semester == semester.name),
  };
};

const getFakeUniversityName = (email) => {
  const number = Math.floor(Math.random() * 2);
  const domain = parseDomain(email);

  switch (number) {
    case 0:
      return "University of " + domain;
    case 1:
      return domain + " State University";
    default:
      return "University of " + domain;
  }
};

const parseDomain = (email) => {
  const emailParts = email.split("@");
  const domainParts = emailParts[1].split(".");

  let name = "";

  domainParts.forEach((part, i) => {
    if (i > 0) {
      name += " ";
    }
    if (i + 1 < domainParts.length) {
      name += part.charAt(0).toUpperCase() + part.slice(1);
    }
  });

  return name;
};

const grades = [
  {
    course: "Calculus 1",
    score: 72,
    letterGrade: "C",
    year: 2021,
    semester: "Fall",
  },
  {
    course: "Intro to Ballroom Dance",
    score: 94,
    letterGrade: "A",
    year: 2021,
    semester: "Fall",
  },
  {
    course: "Computer Science 101",
    score: 65,
    letterGrade: "F",
    year: 2021,
    semester: "Fall",
  },
  {
    course: "Intro to Modern Physics",
    score: 88,
    letterGrade: "B",
    year: 2021,
    semester: "Fall",
  },

  {
    course: "Calculus 2",
    score: 84,
    letterGrade: "C",
    year: 2021,
    semester: "Spring",
  },
  {
    course: "Geometry",
    score: 97,
    letterGrade: "A",
    year: 2021,
    semester: "Spring",
  },
  {
    course: "Computer Science 101",
    score: 76,
    letterGrade: "C",
    year: 2021,
    semester: "Spring",
  },
  {
    course: "Physics II",
    score: 88,
    letterGrade: "B",
    year: 2021,
    semester: "Spring",
  },

  {
    course: "Calculus 3",
    score: 84,
    letterGrade: "C",
    year: 2022,
    semester: "Fall",
  },
  {
    course: "Abstract Algebra",
    score: 97,
    letterGrade: "A",
    year: 2022,
    semester: "Fall",
  },
  {
    course: "Computer Science 102",
    score: 76,
    letterGrade: "C",
    year: 2022,
    semester: "Fall",
  },
  {
    course: "Public Speaking",
    score: 88,
    letterGrade: "B",
    year: 2022,
    semester: "Fall",
  },

  {
    course: "Adv Calculus",
    score: 84,
    letterGrade: "C",
    year: 2022,
    semester: "Spring",
  },
  {
    course: "Geometry",
    score: 97,
    letterGrade: "A",
    year: 2022,
    semester: "Spring",
  },
  {
    course: "Javascript in the Modern Web",
    score: 76,
    letterGrade: "C",
    year: 2022,
    semester: "Spring",
  },
  {
    course: "Cryptography",
    score: 88,
    letterGrade: "B",
    year: 2022,
    semester: "Spring",
  },
];

module.exports = { getGrades };

First, exchangeOktaTokenForFirebaseToken will provide a custom token from Firebase to use in your application. Firebase allows you complete control over your authentication via the signInWithCustomToken method you used on the client. You need to create a custom token using your service account. You downloaded your service account definition as a JSON file earlier. Now you can call createCustomToken from your auth object against your service account. This function requires a uid and optionally accepts other claims you may wish to add. Be mindful that Firebase reserves token names.

You can then obtain a token from the Okta authorization server and present it to the Firebase function to be verified using the OktaJwtVerifier. If the Okta token is valid, you will call your Okta authorization server’s userInfo endpoint to obtain additional information about your user. You can include this information in your Firebase token as its custom claims. Then you can use the firebaseApp object to create your token with those claims. You’ll return this token to the client and sign in with it.

Next, you have the getGrades function. You check context.auth to see if the user is logged in. If they aren’t, you are throwing an error. If they are, allow the user to access the grades data in that file.

There are two different ways to set up functions in Firebase, onCall and onRequest. onRequest gives you a more raw form of handling the incoming call. You’d need to set up your CORS code, your authentication, and all the good stuff that the onCall wrapper takes care of for you. For example, context.auth is provided because you used the onCall, whereas through onRequest you would need to obtain this information manually.

Test your app locally using Firebase emulators

Now you are ready to test your application locally through the Firebase emulator. The emulator will make it so that your services can communicate as though they were deployed to Firebase.

First, edit your .env file to replace REACT_APP_ENV=production with REACT_APP_ENV=development. This change tells the application to connect to the emulator. Next, run the following commands in your project’s root directory.

npm run build
firebase emulators:start

First, you need to build your application since Firebase expects your application to be in the build directory as you configured earlier. Next, it will create an emulator and deploy your function and web application to that emulator. By default, the web app deploys to localhost:5000 rather than the usual localhost:3000.

If you find that you have conflicts with the default ports that Firebase uses, you can update the firebase.json file entries for emulators.functions.port and emulators.hosting.port to ports that you have available. See below for an example firebase.json file that uses port 5002 for hosting and 5001 for functions.

{
  "hosting": {
    "public": "build",
    "ignore": [
      "firebase.json",
      "**/.*",
      "**/node_modules/**"
    ],
    "rewrites": [
      {
        "source": "**",
        "destination": "/index.html"
      }
    ]
  },
  "functions": {
    "source": "functions"
  },
  "emulators": {
    "functions": {
      "port": 5001
    },
    "hosting": {
      "port": 5002
    },
    "ui": {
      "enabled": false
    }
  }
}

You’ll need to open the Okta admin console and navigate to your Okta application to add these ports to the allowlist.

Open Okta Developer Portal and Sign in to Okta. Then press Admin to launch the admin console.

Navigate to Applications > Applications and find your Okta application for this project. Select it to edit. On the General tab, Edit the General Settings with the new ports. For example, if your Firebase hosting port is 5000, add http://localhost:5000/login/callback to Sign-in redirect URIs and http://localhost:5000 to your Sign-out redirect URIs. Update the port number based on your Firebase emulator settings and save.

There’s one more place to add the new port in the Okta admin console. You’ll add the port as a Trusted Origin so your app can complete the logout process. Navigate to Security > API and open the Trusted Origins tab. Press the + Add Origin button and add the new URL with the port, such as http://localhost:5000/. Select the Redirect and CORS checkboxes and save, then return to your application.

At this point, you should be able to log in to Okta, exchange your token for a Firebase token, select a semester, and click Get Grades to see your report card generate.

Screenshot of final project showing a report card for the authenticated user

Deploy your application to Firebase

Once this works, you are ready to deploy your application to Firebase. First, set your .env entry for REACT_APP_ENV back to production if you had set it to development. You may need to run the npm run build command once more in case you’ve made any edits or changes. Once complete, run the command firebase deploy from your root directory.

After completing this step, your CLI will provide a URL to your application. You should see your application running on Firebase if you click that. But, Okta won’t work at this point. You need to go back to your Okta admin portal, under your application, and add {yourFirebaseDomain}/login/callback to your Sign-in redirect URIs, {yourFirebaseDomain} to your Sign-out redirect URIs to the General Settings tab of your Okta application, and add {yourFirebaseDomain} as a Trusted Origin

Now return to your application in Firebase and click Sign In to ensure Okta is hooked up correctly. Once you are signed in, you should be able to select a semester and click Get Grades to see your report card generated.

Review your React app with authenticated Firebase functions

In this article, you learned how to create and deploy a web application to Google Firebase. You also learned how to build Firebase functions as a backend for your web application. Finally, you learned how to secure your SPA using Okta and exchange your Okta access token for a Firebase token that it will know how to use in its pipeline. Here are some related articles that might also be of interest:

Make sure you follow us on Twitter and subscribe to our YouTube channel. Please comment below if you have any questions or want to share what tutorial you’d like to see next.

Okta Developer Blog Comment Policy

We welcome relevant and respectful comments. Off-topic comments may be removed.