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
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.
If you wish, you can follow along with the GitHub repository found here.
oktadev / okta-react-firebase-serverless-example
React application with Okta authentication using Firebase functions deployed to Firebase
Build a React App with Firebase Serverless Functions
This repository shows you how to build a React application secured by Okta and deploy it to Firebase. It also shows you how to set up Functions in Firebase to exchange and Okta accessToken for a Firebase token and then call a secured route using Firebase auth.
Please read Build a React App with Firebase Serverless Functions to see how it was created.
Prerequisites:
Okta has 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.
Getting Started
To pull this example, first create an empty github repo. Next run the following commands:
git clone --bare https://github.com/oktadev/okta-react-firebase-serverless-example.git
cd okta-react-firebase-serverless-example
npm ci
…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:4280/login/callback
for the Redirect URI and set the Logout Redirect URI to http://localhost:4280
.
NOTE: You can also use the Okta Admin Console to create your app. See Create a React App for more information.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:4280
. 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
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.
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.
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:
- Language - Javascript
- EsLint - No (You should use this in production-ready applications.)
- When prompted to install dependencies say Yes
Next, select the following options to set up hosting.
- What do you want to use as your public directory? - build
- Configure as a single-page app? yes
- 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.
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:
- How to Build and Deploy a Serverless React App on Azure
- Build a CRUD App with Angular and Firebase
- Use Firebase with Your ASP.NET MVC App
- Build Reusable React Components
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.
Original post written by Nickolas Fisher for the Okta Developer blog.
Top comments (0)