Most apps that you build with Firebase’s backend services, such as Realtime Database, Cloud Firestore, and Cloud Storage, need some way to sign users in: among other things, this lets you provide a consistent experience across sessions and devices, and lets you set user-specific permissions. Firebase Authentication helps you meet this requirement by providing libraries and services that you can use to quickly build a new sign-in system for your app.
But what if your organization already uses a service such as Okta to handle user identity? With Firebase Custom Authentication, you can use any user identity service (including Okta) to authenticate with Firebase, and this post will show you how.
You’ll learn how to build a Firebase and Okta integration, which will have two components:
- A Node.js backend that “exchanges” Okta access tokens for Firebase custom authentication tokens. The backend is an Express.js app that you can deploy as a Cloud Function or run on your own infrastructure.
- A web frontend that signs users in with Okta, gets a Firebase custom authentication token from your backend, and authenticates with Firebase using the custom token.
By the way, this approach can also be used with some modification for other identity services, such as Auth0, Azure Active Directory, or your own custom system.
Ready to get started? Great! But, before you write any code, you’ll need to set up your Okta and Firebase projects.
I hope you like consoles
First, set up an Okta project on the Okta Developer site:
-
Sign in or sign up for a new account.
-
Take note of your Org URL (top-right of the dashboard) for later.
-
On the Applications page, add a Single-Page App.
Set the Base URIs and Login redirect URIs to the location where you plan to host your web frontend (
http://localhost:5000
if you’re using the Firebase Hosting emulator) and enable the Authorization Code grant type.When you’re done, take note of the app’s Client ID for later.
-
In API > Trusted Origins, confirm that the base URI you set above is listed, with CORS and Redirect enabled.
Then, set up a Firebase project in the Firebase console:
-
Open your Firebase project or create a new one. Take note of your project ID for later.
-
On the Project Overview page, add a new web app.
If you plan to eventually host your web app with Firebase, you can automatically set up Firebase Hosting and simplify configuration by enabling Also set up Firebase Hosting for this app.
-
If you plan to test your token exchange endpoint locally, such as by using the Cloud Functions emulator (recommended), open your project settings and, on the Service Accounts page, generate and download an Admin SDK service account key. Be sure to keep this file safe, as it grants administrator access to your project.
Finally, if you plan to deploy your token exchange endpoint as a Cloud Function:
- Enable the IAM Service Account Credentials API in the Google Cloud console.
- After you deploy your Cloud Function, you will also need to make sure it is configured to run as a service account with the Service Account Token Creator role. See the sample app documentation for details.
Now that your projects are set up, you’ll write the crucial piece: the token exchange endpoint.
Okta tokens in; Firebase tokens out
The job of the token exchange endpoint is to take a user’s Okta access token and, if it’s valid, produce a Firebase custom authentication token that represents the same user.
This endpoint needs to be able to verify the authenticity of the Okta access token. To accomplish this, use the Express.js middleware provided in Okta’s developer documentation (reproduced below, with minor modifications):
const OKTA_ORG_URL = // Your Okta org URL
const OktaJwtVerifier = require('@okta/jwt-verifier');
const oktaJwtVerifier = new OktaJwtVerifier({
issuer: `${OKTA_ORG_URL}/oauth2/default`
});
// Middleware to authenticate requests with an Okta access token.
const oktaAuth = async (req, res, next) => {
const authHeader = req.headers.authorization || '';
const match = authHeader.match(/Bearer (.+)/);
if (!match) {
res.status(401);
return next('Unauthorized');
}
const accessToken = match[1];
try {
const jwt = await oktaJwtVerifier.verifyAccessToken(
accessToken, 'api://default');
req.jwt = jwt;
return next(); // Pass the request on to the main route.
} catch (err) {
console.log(err.message);
res.status(401);
return next('Unauthorized');
}
}
Any endpoint protected by this middleware will require a valid Okta access token in the Authorization
header. If the token is valid, it will insert the decoded token into the request before passing the request along by calling next()
.
Now, you can write the token exchange endpoint:
const express = require('express');
const app = express();
const cors = require('cors')({origin: 'https://YOUR_DOMAIN'});
const firebaseAdmin = require('firebase-admin');
const firebaseApp = firebaseAdmin.initializeApp();
// Get a Firebase custom auth token for the authenticated Okta user.
// This endpoint uses the `oktaAuth` middleware defined above to
// ensure requests have a valid Okta access token.
app.get('/firebaseCustomToken', [cors, oktaAuth], async (req, res) => {
const oktaUid = req.jwt.claims.uid;
try {
const firebaseToken =
await firebaseApp.auth().createCustomToken(oktaUid);
res.send(firebaseToken);
} catch (err) {
console.log(err.message);
res.status(500).send('Error minting token.');
}
});
This endpoint uses the Firebase Admin SDK to mint a Firebase custom authentication token using the user’s Okta UID. When you sign a user in with this token for the first time (on the frontend), Firebase Authentication will add a user record with the same UID to your project.
This process of using an Okta access token to acquire a Firebase custom token is the key idea behind integrating Okta and Firebase. But, let’s go one step further and write a simple web frontend to demonstrate the use of the endpoint.
A minimal web frontend
The demo frontend is a plain HTML and JavaScript web app that uses the Firebase Authentication Web SDK and Okta’s sign-in widget library.
Start with two containers: one for authenticated user content and one for Okta’s sign-in widget:
<div id="authenticated-user-content" hidden>
<h2>Authenticated with Firebase</h2>
<p id="user-info"></p>
<button onclick="firebase.auth().signOut();">Sign out</button>
</div>
<div id="signin-widget" hidden></div>
Set up a Firebase authentication state listener that shows some user profile information to signed-in users and Okta’s sign-in widget to signed-out users:
const oktaSignIn = new OktaSignIn({
baseUrl: OKTA_ORG_URL,
redirectUri: window.location.url,
authParams: {
display: 'page',
},
el: '#signin-widget',
});
firebase.auth().onAuthStateChanged((user) => {
if (user) {
// User is signed in. Display some user profile information.
document.getElementById('user-info').innerHTML =
`Hi, ${user.displayName}! Your email address is
${user.email} and your UID is ${user.uid}.`;
document.getElementById('authenticated-user-content').hidden = false;
document.getElementById('signin-widget').hidden = true;
} else {
// User is signed out. Display the Okta sign-in widget.
oktaSignIn.showSignInToGetTokens({
clientId: OKTA_CLIENT_ID,
redirectUri: window.location.url,
getAccessToken: true,
getIdToken: true,
scope: 'openid profile email',
});
document.getElementById('authenticated-user-content').hidden = true;
document.getElementById('signin-widget').hidden = false;
}
});
When a user signs in with Okta’s widget, their browser briefly redirects to Okta’s authorization server, and then, assuming the user signed in successfully, redirects back to your app with the response.
Use Okta’s sign-in library to get the Okta access token from the response and use the access token to get a Firebase custom token from your token exchange endpoint:
if (oktaSignIn.hasTokensInUrl()) {
// Get the access token from Okta.
const oktaTokenResponse =
await oktaSignIn.authClient.token.parseFromUrl();
const accessToken = oktaTokenResponse.tokens.accessToken.value;
// Use the access token to call the firebaseCustomToken endpoint.
const firebaseTokenResponse = await fetch(CUSTOM_TOKEN_ENDPOINT, {
headers: {
'Authorization': `Bearer ${accessToken}`,
}
});
const firebaseToken = await firebaseTokenResponse.text();
// (Continued below.)
}
And finally, authenticate with Firebase using the custom token:
// (Continued from above.)
try {
await firebase.auth().signInWithCustomToken(firebaseToken);
} catch (err) {
console.error('Error signing in with custom token.');
}
When the call to signInWithCustomToken()
completes, the auth state listener will detect the change and display the user’s profile information.
At this point, the user is authenticated with Firebase and you can use any of Firebase’s authentication-enabled services, such as Realtime Database, Cloud Firestore, and Cloud Storage. See the Security Rules documentation for more information on granting resource access to authenticated users.
For the complete demo app and backend that the code snippets above came from, see the Authenticate with Firebase using Okta sample on GitHub.