February 23, 2023

OAuth Authentication in HarperDB using Auth0 & Node.js

Welcome to Community Posts
Click below to read the full article.
Arrow
Summary of What to Expect
Table of Contents

Introduction

In the last article of this series we saw how to authenticate users using oAuth in HarperDB. In this article we will go a step further and modify our custom function implementation to allow us to have a different provider, in this case we’ll use Auth0 by Okta.

For this, we’ll continue to use the oAuth 2.0 protocol, which is the most common protocol used for authentication and authorization. We will implement this protocol using the Authorization Code Grant flow, which is the most secure and recommended flow for web applications inside a HarperDB custom function.

This will allow us to authenticate our users using Auth0, allowing us to modify our authentication flow to use different providers, such as Google, Facebook, Twitter, etc.

Prerequisites

I’ll assume that you have already read the previous article and have a basic understanding of how oAuth works. If you haven’t read it yet, I recommend you do so before continuing.

I’ll also assume that you have a HarperDB instance running, for this you can follow the instructions in the “Creating the Infrastructure” section of the previous article literally, without any modifications.

Even though most of the initial setup is the same, some of the steps will be different, for example, our GitHub application will now need to point to our Auth0 application instead of our HarperDB instance.

The final code for this application can be found in the GitHub repository at https://github.com/HarperDB-Add-Ons/harperdb-auth0-integration. You can clone the repository and follow along with the code, or you can just copy and paste the code from the repository.

Let’s set it all up.

Creating the Auth0 Application

Before we can do anything, we need to create an Auth0 application. To do this, we’ll need to create an account on Auth0’s website. To do this, go to https://auth0.com/ and click on the “Sign Up” button.

After creating your account, you’ll be redirected to the Auth0 dashboard. From here, click on the “Applications” menu item on the left side of the screen. You can also be presented with a banner like the one below, in which case you can click on the “Create Application” button.

If not, the application menu lies on the left side of the screen and you can click on the “Create Application” button. And you’ll be prompted to choose the type of application you want to create.

We’ll choose the “Regular Web Application” option, which is the most common type of application. After choosing the type of application, you’ll be prompted to enter the name of your application. You can choose any name you want, but I’ll use “HarperDB Auth” for this example.

Next up, you’ll land in the getting started page of that application, here you’ll be able to choose between several frameworks, let’s choose Express for this example and you’ll be presented with a page like the one below. Which is the “Getting Started” page for the Express framework.

In this page, we’ll need to define and register our application URLs. The first URL is the “Allowed Callback URLs”, which is the URL that Auth0 will redirect the user to after the authentication process is complete. In our case, this will be the URL of our HarperDB instance, so we’ll use the following URL:

http://localhost:9926/oauth/callback

The second URL is the “Allowed Logout URLs”, which is the URL that Auth0 will redirect the user to after the logout process is complete. In our case, this will be the URL of our HarperDB instance, so we’ll use the following URL:

http://localhost:9926

Having done that, we can now click on the “Save Changes” button at the bottom of the page. After clicking on the button, you’ll be redirected to the application settings page, where you’ll be able to see the “Domain”, Client Secret, and “Client ID” of your application. We’ll need these values later, so make sure you save them somewhere.

Now that we have our Auth0 application created, we can move on to the next step.

Creating the GitHub integration

Auth0 allows us to create several social connections for log in. In the other example we used GitHub for log in, so we’ll do the same here. To do this, we’ll need to go to the “Authentication” menu item on the left side of the screen and click on the “Social” tab. Then click the big “+ Create Connection” button. You’ll be presented with a list of social connections, we’ll choose “GitHub” for this example.

After clicking on the “GitHub” button, you’ll be presented with a page like the one below. Here you’ll need to enter the “Client ID” and “Client Secret” of the application we’ll create in GitHub, but for now, we don’t have anything so we’ll leave it as it is.

Now go to your GitHub developer profile at https://github.com/settings/developers and choose “OAuth Apps” from the menu on the left side of the screen. Then click on the “New OAuth App” button.

In the new menu that appears, we need to configure our application with the Auth0 application we just created. So we’ll enter the values we received from our application dashboard.

The “Homepage URL” will be the URL of our Auth0 tenant. This can be found in your application settings page under the “Domain” field. The “Authorization callback URL” will be the same domain as the “Homepage URL” but with the following path: “/login/callback”

After entering the values, click on the “Register application” button. You’ll be presented with a page like the one below, which contains the “Client ID” and “Client Secret” of your application.

At first, the client secret won’t be shown, but you can click on the “Generate a new client secret” button to generate a new one. After generating the new client secret, you’ll be able to see it in the page. Copy it right away because it won’t be shown again.

Now go back to your Auth0 dashboard, in the “GitHub” connection page, enter the “Client ID” and “Client Secret” of your GitHub application and click on the “Save” button.

We can try the new connection by clicking the “Try” button. You’ll be presented with a page like the one below, where you can log in with your GitHub account.

After logging in, you’ll be redirected to the Auth0 success page, where you’ll be able to see the user that just logged in.

After saving, go back to your application page and go to “Connections” tab. You’ll be presented with a page like the one below, where you can see the connections that are enabled for your application. Enable the GitHub connection we just created on the toggle button.

Now that we have our GitHub connection enabled, we can move on to the next step. Let’s do some code!

Creating the OAuth application

Assuming we’re working with the same structure as the other example, when you run your docker-compose file, you’ll have a folder called “harperdb” in the root of your project. This will be an empty directory and, in there, we will clone the same repository we used in the other example.

To do this, we’ll need to open a terminal and navigate to the “harperdb” directory. Then we’ll clone the repository with the following command:

Be aware that we need to clone the repository to a new directory called “oauth”. That will be the entrypoint for our custom function URL, so we will call it as http://localhost:9926/oauth/<route>.

Once you have cloned the repository, we’ll need to remove the Fastify oAuth plugin because we won’t be using that anymore, and install the missing dependencies, we can do that with just one command, that you need to run inside the “oauth” directory:

Adjusting the config

Next up, we will adjust our configuration file. First, rename the file .authConfig.json.example to .authConfig.json. Then we need to set up our variables, secrets and everything else, but we will also remove some keys that were used in the previous plugin that we don’t need anymore, and add a new key for our Auth0 application.

The final file should be something like this:

{
  "client": {
    "id": "your-auth0-client-id",
    "secret": "your-auth0-client-secret"
  },
  "loginPath": "/login",
  "auth0ApplicationUrl": "https://your-auth0-url..auth0.com",
  "callback": "http://localhost:9926/oauth/callback",
  "schema": "hdb_auth",
  "table": "sessions",
  "salt": "some 64bit-long random string here",
  "logout": "/logout"
}

You can get all these informations in the Auth0 dashboard, in the application settings page, like we saw before.

Creating the authorization endpoint

The oAuth authorization endpoint is the endpoint that will be called when the user clicks on the “Log in” button. This endpoint will redirect the user to the Auth0 login page, where the user will be able to log in with the social connection we enabled before.

There are easier ways to do this, but we’ll do it the hard way, just to show you how it works. We will need to add a new /login route to our application, to do that we will first remove all the code that’s referencing the previous fastify-oauth2 plugin, because this plugin automatically did the authorization for us.

For that we need to go to the helpers directory and open the authHelper.js file, there we will remove everything from line 86 to 96 in the loadRoutes function and also remove the import for the fastify-oauth2 plugin.

So this is the code we will remove from the function:

server.register(oauthPlugin, {
  name: 'githubOAuth2',
  credentials: {
    client: CONFIG.client,
    auth: oauthPlugin[CONFIG.provider]
  },
  // register a server url to start the redirect flow
  startRedirectPath: CONFIG.loginPath,
  // facebook redirect here after the user login
  callbackUri: CONFIG.callback
})

In its place we will add the following code:

server.get('/login', async function (_, reply) {
  const url = `${CONFIG.auth0ApplicationUrl}/authorize?response_type=code&client_id=${CONFIG.client.id}&redirect_uri=${CONFIG.callback}`
  reply.redirect(url)
})

This code will redirect the user to the Auth0 login page, where the user will be able to log in with the social connection we enabled before.

So, for now, our function will look like this:

const loadRoutes = async ({ server, hdbCore }) => {
    server.get('/login', async function (_, reply) {
        const url = `${CONFIG.auth0ApplicationUrl}/authorize?response_type=code&client_id=${CONFIG.client.id}&redirect_uri=${CONFIG.callback}`;
        reply.redirect(url);
    });

    const callback = CONFIG.callback.split('/').pop();
    server.get(`/${callback}`, async function (request, reply) {
  // Rest of the code

Creating the callback endpoint

When we navigate the user to the /authorize endpoint in Auth0, we’ll receive back a code that we will exchange for the access token. This code will be sent to the callback endpoint as a query string, which will be the endpoint that we set in the Auth0 application settings.

This part is currently done by the getAccessTokenFromAuthorizationCodeFlow function, but we will need to change it a little bit to work with Auth0 since this is part of the old plugin. So we will remove the call to this function inside the authHelpers.js file and create our own authentication function.

Let’s then create a new file called auth0.js in the helpers directory. This file will contain the function that will be responsible for the authentication process.

import axios from 'axios'

export const getAccessToken = async (auth0Url, auth0ClientId, auth0ClientSecret, callbackUrl, code) => {
  const tokenExchangeUrl = `${auth0Url}/oauth/token`
  const response = await axios.request({
    method: 'POST',
    url: tokenExchangeUrl,
    headers: {
      'content-type': 'application/x-www-form-urlencoded'
    },
    data: new URLSearchParams({
      grant_type: 'authorization_code',
      client_id: auth0ClientId,
      client_secret: auth0ClientSecret,
      code,
      redirect_uri: callbackUrl
    })
  })
  return response.data
}

This function is basically calling the auth0 token exchange endpoint, passing the code we received from Auth0 as a form data, and exchanging it for the access token.

Back to our authHelpers.js file, we will import the getAccessToken function and replace the call to the getAccessTokenFromAuthorizationCodeFlow function, this is the code for the whole /callback route:

const callback = CONFIG.callback.split('/').pop()
server.get(`/${callback}`, async function (request, reply) {
  const {
    query: { code }
  } = request

  if (!code) return reply.code(400).send('Missing code')

  const { access_token } = await getAccessToken(
    CONFIG.auth0ApplicationUrl,
    CONFIG.client.id,
    CONFIG.client.secret,
    CONFIG.callback,
    code
  )

  const hdbToken = await makeHash(access_token)
  const hdbTokenUser = (await randomBytesAsync(12)).toString('hex')
  const hashedToken = await makeHash(hdbToken)

  await hdbCore.requestWithoutAuthentication({
    body: {
      operation: 'insert',
      schema: CONFIG.schema,
      table: CONFIG.table,
      records: [{ user: hdbTokenUser, token: hashedToken }]
    }
  })

  return `${hdbTokenUser}.${hdbToken}`
})

The part that we changed is this:

const {
  query: { code }
} = request

if (!code) return reply.code(400).send('Missing code')

const { access_token } = await getAccessToken(
  CONFIG.auth0ApplicationUrl,
  CONFIG.client.id,
  CONFIG.client.secret,
  CONFIG.callback,
  code
)

We are getting the code from the query string, and then we are calling the getAccessToken function that we created before, passing the code we received from Auth0. This function will return the access token, which we will use to create the token that will be used to authenticate the user in the schema. All te rest of the code is the same as before.

Testing our application

To test our application we can go first to the HarperDB studio and go to the functions tab, there we will restart the server by clicking the “Restart Server” button.

After that, we will call the /setup endpoint to create the schemas we need on the http://localhost:9926/setup. This will create a schema called hdb_auth with a sessions table. Then we can go to the http://localhost:9926/login endpoint and log in with the social connection we enabled before.

In the end we will be presented with a token page like this:

Now we can copy this token, and issue a request to the /create/schema/:schemaname endpoint, passing the token in the Authorization header, like this:

curl -X GET \
  'http://localhost:9926/oauth/create/schema/dogs' \
  --header 'Authorization: harperdb token.goeshere'

This will create the dogs schema:

We can create a table called breeds in this schema, and insert some data:

curl -X GET \
  'http://localhost:9926/oauth/create/table/dogs/breeds' \
  --header 'Authorization: harperdb token.goeshere'

This will create the table for us:

And then we can insert some data:

'http://localhost:9926/oauth/dogs/breeds' \
  --header 'Authorization: harperdb token.goeshere' \
  --header 'Content-Type: application/json' \
  --data-raw '[
  {
    "name": "Labrador Retriever"
  },
  {
    "name": "German Shepherd"
  }
]'

Now we have our data inserted and validated with Auth0:

Conclusion

In this tutorial we have seen how to use Auth0 to authenticate users in HarperDB. We have seen how to create a schema and a table, and how to insert data in the table. We have also seen how to use the authHelpers.js file to make the authentication process easier.

You can extend this code and even implement your own authentication architecture, using Auth0, or any other authentication provider extremely easily. Stay tuned for more tutorials on HarperDB!