Setting up SSO authentication in Fauna with Auth0
By: Fauna Inc
Tags: Authentication, Full Stack, Serverless, Faunadb

Offering users alternative ways to authenticate, such as social login, is valuable for both the end-user and for you the developer since it has the potential to reduce the amount of support cases around lost passwords or blocked accounts.

Although you could implement these flows on top of Fauna, it’ll require some work and when it comes to security, it makes sense to let libraries of experts in the field handle that work for us. Identity providers, such as Auth0, do a great job at providing libraries for this functionality out-of-the-box. In this article, we’ll set up a skeleton application with Auth0 and take advantage of Fauna’s capabilities to accept tokens from external Identity Providers.

We will replace our authentication logic with Auth0’s excellent React SDK which will take care of the login/signup flows for us. Instead of implementing login/signup forms ourselves, the Auth0 libraries will provide us with this functionality out of the box, including social login.

In this first part, we’ll start with pure authentication, in the second article, we’ll dive into different options for authorization. In the authorization part, we’ll use Auth0’s capabilities to augment JWTs (via roles, rules and permissions) and use Fauna’s ABAC roles to interpret these JWT attributes. The complete skeleton application can be found on GitHub.

Setup

To get started, we’ll configure a Fauna database and the necessary Auth0 resources to make them play nicely together.

Set up a Fauna database for Auth0

The first thing we will need is a Fauna database. Log in to the dashboard (or sign up if this is the first time you are using Fauna).

Create a new database by clicking the New Database button and filling in a name in the form that follows:

Installing the repository

In case you want to follow along, clone the code repository since it contains setup scripts that will help configure your Fauna database in 5 minutes.

git clone git@github.com:fauna-brecht/faunadb-auth-skeleton-frontend-with-auth0.git

The repository contains two main folders, fauna-queries and frontend. Anything that is fauna related can be found in fauna-queries, including the setup scripts. This keeps the fauna code cleanly separated and allows us to easily change the skeleton later on with a backend approach or swap out the frontend framework without changing the fauna code.

Both folders have their own package.json and require their own .env.local file (for which .example files are provided). No worries, we’ll explain how to configure these in the next sections.

Running the setup script

An example that uses data won’t operate without Fauna collections, indexes, and access providers to work. However, the repository provides a convenient script that sets up all these resources for you. The script requires two environment variables which you need to configure in the .env.local file. The script provides some optional variables as well for your convenience which are explained in the README.

These variables are:

FAUNADB_ADMIN_KEY: the admin key that will be used by the script to access your Fauna database and set up resources.

AUTH0_DOMAIN: the Auth0 domain which will be used to create the Access Provider (see later).

The Auth0 domain is typically a URL derived from your account of the form .auth0.com. We can find the account in the upper right corner of our Auth0 dashboard, in our case faunadb-auth0.

The Admin key for your database can be created in the Fauna dashboard by going to the SECURITY menu and clicking NEW KEY. Make sure to select the Admin role since our key requires full access to the database.

Fill in a name, press Save, and you will receive a new key. Make sure to copy it since you will only receive this key once. Since this key will have full access to your database, make sure to keep it somewhere safe.

Once we have the env variables configured, we can simply run:

npm run setup

This script will set up everything for us, it’ll create the collections, indexes, and roles to run the application and will even load some dummy data for us to test out the functionality. Most importantly, it will create an access provider that allows us to use Auth0 tokens to access Fauna.

Access providers

The script will create an access provider resource. Once we run the script, let’s take a look in the dashboard at the access provider that was created by going to Security, PROVIDERS, and clicking the gear icon next to the created provider.

The provider view shows us the properties that were set for the access provider.

Name: a unique name for your access provider. Issuer: the authority that will provide us with the tokens, in this case the Auth0 domain. JSON Web Key Secret URI: the JWTs from Auth0 are signed which prevents a malicious actor to change the tokens. This URI is where Auth0 will expose the public keys which Fauna can use to verify that the JWTs are not tampered with. Typically, as is the case for Auth0, such a URI consists of the issuer’s domain followed by .well-known/jwks.json. Roles: the roles that will define which permissions the Auth0 tokens will receive.

Creating the Access Provider informs our database that it should accept JSON Web Tokens (JWTs) from an external identity provider and specify what permissions such a token will receive. The issuer (2) and jwks endpoint (3) the type of tokens we will accept while the roles (4) define which permissions such a token will receive.

Audience

We can also see that an access provider has an audience which is a field not specified by the script but was instead automatically generated by Fauna.

The audience uniquely identifies your Fauna database and needs to be on the Auth0 JWT token in the list of audiences (the aud attribute). The default Auth0 access token will only contain one default audience:

{

"iss": "https://Fauna-auth0.auth0.com/",

"sub": "google-oauth2|107696438605329289272",

"aud": [

"https://Fauna-auth0.auth0.com/userinfo"

],

"iat": 1602681059,

"exp": 1602767459,

"azp": "OgU7xmvv7pwumxlbilTA4MB7pErILWfS",

"scope": "openid profile email",

}

For Fauna to accept Auth0 token, it needs to contain the audience as follows:

{

...

"aud": [

"https://Fauna-auth0.auth0.com/userinfo",

"https://db.fauna.com/db/yxxeeaaqcydyy",

],

...

}

This can be done by creating an Auth0 API and asking Auth0 to include the audience when we configure the Auth0 library (e.g. in this case when we’ll configure the @auth0/auth0-react library). First, we’ll need to create an Auth0 application and an Auth0 API so let’s head over to the Auth0 dashboard.

Set up an Auth0 application

Go to https://auth0.com/ and sign up for a new account or login to your existing account.

Once you are logged in, you should end up on the following screen.

Auth0 needs to know a few things about your application before they can provide your application with a great login experience. Therefore, we have to let Auth0 know that we are going to build an application that will log in with Auth0 by creating a new application. To do so, go to Applications in the menu on the left.

Creating an application

Configuring your application

Since the SDK will redirect to Auth0, we need to tell Auth0 how to return to our application which is done by setting a redirect URI in Settings.

Specify the callback where you want Auth0 to redirect to upon a successful login. Upon a redirect, Auth0 sets a code in the URI which you would normally use to retrieve the access or ID token as a part of the OAuth flow. Good news, this is completely taken care by their React SDK. As long as you place the React component on the top level of your application, you can just redirect to the SPA’s base URL, which in our case is: https://localhost:3000

Besides the callback URL, in order to silently refresh and logout we have to specify an Allowed Logout URL and Allowed Web Origin as well. Due to our setup we can also simply use the SPA’s base URL here.

Note: we are currently developing this application locally and therefore hosting on localhost. Some features such as skipping the consent page can not be done when hosting from localhost and/or from regular HTTP. To enable these features you need a non-localhost HTTPS URL.

Finally, take note of the Domain and Client ID which you can find in the Settings > Basic Information pane, we will need these in the next steps to configure our SPA application. We will not use the Client Secret here since we do not have a backend. The client secret is typically used when you request access tokens via the backend.

Set up an Auth0 API

Next we need to set up an Auth0 API. Remember the audience that needed to be present in the token? To add an audience to the token, we need to set it up as an API in Auth0. Setting up an API is done by selecting APIs in the menu, right next to the Applications entry.

In the next step, the only thing we have to do is fill in a name and the identifier.

Make sure that the Signing Algorithm is set to RS256, this is a requirement for JWT Tokens to work with Fauna.

Since we are defining an API for our Fauna database, the identifier will be the audience that uniquely identifies our database. This is the ‘audience’ value that we can find in the Access Provider.

Now that we have an API defined for our Fauna database, we can now ask Auth0 to include this API as an audience in the access token. Asking Auth0 to do that is done in the configuration of our SPA application, which means it’s time to dive into our frontend!

Build our frontend

Set up the frontend

Just like we configured our setup script, the frontend requires a few environment variables to function. Go ahead and copy the provided .env.local.example file.

The AUDIENCE variable should be filled in with the audience which we received when we created the access provider. This is the same identifier we filled in when we created our API in the previous steps.

The DOMAIN and CLIENTID can be found in your Auth0 application UI.

In our case, these variables are filled in as follows. Replace the values with the values from your specific applications. Be careful, the URLs are sensitive, adding a slash in the end can break the functionality since the audience has to match exactly.

Running the app

Once we have correctly specified these variables we can now run the frontend with the following command:

npm run start_frontend

And we are greeted with the following UI:

Since we are not logged in yet, we don’t have access to Fauna resources and the sad dinosaur informs us about that.

Once we go to the login tab and press the login button we get redirected to the Auth0 login page. Since we didn’t specify any users yet in Auth0 we can’t log in yet with email/password but we can sign in using our Google account.

Once we log in, we get redirected to our application, and we will see dinosaurs.

Why did I get access?

Now that we have a working application, it’s time to dive into the setup script and see what it has done for us. The dinosaurs we see on the screen are simply a collection within Fauna which was created and populated with cute dinosaurs at the moment you ran the setup script.

The access provider that was created when we ran the setup script specified a role called loggedin_basic.

Note: CreateOrUpdateProvider is not an FQL function but a small helper we defined ourselves.

And since the role defines simple read access to the whole collection, we get access to all dinosaurs, regardless of who is logged on.

In a later section on Authorization we will provide different options to precisely define different access roles depending on the logged-in user. Let’s first take a look at the frontend code to see how the code looks that makes this work.

How does it work?

First we configured our React App

We have defined a few environment variables which you had to fill in to start the frontend. The frontend code uses these environment variables to configure the React SDK from Auth0. Configuring the library in React is surprisingly easy, we import the Auth0Provider component from the @auth/auth0-react NPM library and insert the React component at the highest level possible in our application.

import { Auth0Provider } from '@auth0/auth0-react'



const App = () => {

return (

<Auth0Provider

domain={process.env.REACT_APP_LOCAL___AUTH0_DOMAIN}

clientId={process.env.REACT_APP_LOCAL___AUTH0_CLIENTID}

audience={process.env.REACT_APP_LOCAL___AUTH0_AUDIENCE}

redirectUri={window.location.origin}>

...

https://github.com/fauna-brecht/faunadb-auth-skeleton-frontend-with-auth0/blob/default/frontend/src/app.js

When we log in, we redirect to Auth0

When press the LOGIN button in our UI, we need to redirect our users to the Auth0 login form.

When we take a look at that button in our code there is an onClick handler defined. The handler is directly assigned to the loginWithRedirect function which is a convenient function provided by the auth0-react library to handle the redirection for you.

import { useAuth0 } from '@auth0/auth0-react'



const Login = props => {

const { loginWithRedirect } = useAuth0()



...

<button onClick={loginWithRedirect} className="login">

Login

</button>

...

When the loginWithRedirect function is called, the user will be redirected to the Auth0 login form.

Upon successful login, we receive the user information and a token

Once the user has logged in, we will be redirected to our application. We would typically have to take care of a few manual steps such as intercepting a code and retrieve an ID token and access token manually. Good news again, the Auth0Provider component is intelligent and takes care of that work automatically as long as we included the Auht0Provider on the top-level of our React DOM. It even has a React Context under the hood which will automatically store the logged-in user. The only token we still need to retrieve ourselves is the access token, this is the token we need to call Fauna.

In the Home page code (the page with the dinosaurs), we will again use a number of provided Auth0 functions which are exposed via the useAuth0 React hook. In our approach we will retrieve the ‘getAccessTokenSilently’ function and pass it on to our data fetching function ( fetchDinos).

import { useAuth0 } from '@auth0/auth0-react'



const Home = () => {

const { getAccessTokenSilently } = useAuth0()



useEffect(() => {

...

fetchDinos(getAccessTokenSilently)

}

The fetchDinos function will call getAccessTokensSilently which will provide us with an Auth0 access token which we can use to call Fauna.

async function fetchDinos(getAccessTokenSilently) {

try {

const token = await getAccessTokenSilently()

...

Debugging

We have added the console.log statements, so feel free to open your browser console to see how the user and token looks in your browser’s developer console. The token — a long string that represents the JWT — can be decoded, for example with https://jwt.io/ to look at the content. Since we have configured the Auth0Provider component to include the audience (with our environment variables), the token should contain the database audience, make sure this exactly matches the audience that we received from the Fauna Access Provider (make sure there are no trailing slashes).

Presenting the token to Fauna

Once the token is received we will use it to query the database to retrieve the data.

async function fetchDinos(getAccessTokenSilently) {

try {

const token = await getAccessTokenSilently()

faunaQueries.setToken(token)

const dinos = await faunaQueries.getDinos()

...

The faunaQueries.setToken function is simply a helper function that will create a new Fauna client with the JavaScript driver.

import Fauna from 'Fauna'



class QueryManager {

...

setToken(auth0AccessToken) {

this.client = new Fauna.Client({ secret: auth0AccessToken })

}

How to enable silent refresh

Silent refresh should work automatically when logging in with credentials. However, if we would log in using social login (e.g. google) and we refresh the page, we will lose our session. This happens because Auth0 makes it easy for us and provides developer credentials to perform social login out-of-the-box. However, silent refresh will not work until we provide our own real credentials.

In case you want to configure your social account properly for production, this Auth0 guide explains how to obtain these credentials.

Conclusion

This article describes how to authenticate users with Fauna and Auth0 which provides us with SSO out-of the box. Since we have only covered authentication in this post, we will dive into authoraizations patterns to secure your data by combining Autho0 roles and premissions in part 2 of this article.