HarperDB Authentication with OAuth using Custom Functions

HarperDB Custom Functions enable users to authenticate with any auth scheme such as OAuth. This tutorial will outline the steps to creating a custom auth system.

Outline

  • Summary
  • Introduction
  • Creating the infrastructure
  • Registering the database in the studio
  • Creating the oAuth application in GitHub
  • HarperDB oAuth custom functions
  • How to use the custom function
  • Manipulating the database
  • Creating data
  • Reading data
  • Logging out
  • Conclusion

Introduction

When we are talking about HarperDB, we cannot leave out the amazing Custom Functions feature. It allows us to create our own internal API that lives inside HarperDB and it has access to the database. This is a very powerful feature that allows us to create a lot of things, like a custom authentication system.

HarperDB already has a built-in function to authenticate users using Basic and JWT authentications, but what if we could use an external provider such as Google, Facebook, or GitHub?

In this article, we will see how to create a custom authentication system using OAuth. We will use the oAuth 2.0 protocol, which is the most used protocol for authentication and authorization. We will then use the HarperDB Custom Functions to create a custom authentication system that will allow us to authenticate users using OAuth.

Creating the infrastructure

Since we’re going to use HarperDB locally, we need to create a local instance of it so we can start developing our application. For that, we will use Docker and Docker Compose.

First, let’s create a new directory and create a new file called docker-compose.yml inside it. This file will contain the configuration for our local HarperDB instance.

services:
  harper:
    image: harperdb/harperdb:latest
    container_name: harperdb
    ports:
       - 9925:9925
       - 9926:9926
    volumes:
       - ./harperdb:/opt/harperdb/hdb/custom_functions
    environment:
       - HDB_ADMIN_USERNAME=admin
       - HDB_ADMIN_PASSWORD=admin
       - CUSTOM_FUNCTIONS=true
       - LOG_LEVEL=error
       - RUN_IN_FOREGROUND=true

In this file we are telling Docker to spin up a container using the harperdb image and we are enabling the CUSTOM_FUNCTIONS environment variable. This will allow us to create our own custom functions inside the container.

Another important step is that we are creating a volume that will allow us to manipulate the custom function files inside the container. This is very important because we will be able to create our custom functions inside our local machine and they will be available in the created container, as well as persist across container restarts.

Now that we have our docker-compose.yml file, we can start our local HarperDB instance by running the following command:

docker-compose up

This command will start our local HarperDB instance and it will be available at http://localhost:9925. However, the custom instance API port is defined at port 9926, we need to keep that in mind.

Registering the database in the studio

Now that we have our local HarperDB instance running, we need to register it in the HarperDB Studio. To do that, we need to go to the HarperDB Studio and click on the “Add Instance” button.

Then we will select “Add a new user-installed instance” and fill the form with our login information.

You can find both the username and the password in the docker compose file, in the HDB_ADMIN_USERNAME and HDB_ADMIN_PASSWORD environment variables. The connection URL will be http://localhost:9925 and make sure not to use SSL since we are in a local environment and we don’t have a valid SSL certificate.

Just select “Instance details” and select the “free” plan.

After the database is added, we can see that, in the top menu, we have a “functions” tab. This is where we will be able to create our custom functions.

Creating the oAuth application in GitHub

OAuth is, by definition, what we call a three-legged authentication. This means that we need to have three parties involved in the authentication process: the user, the client, and the authorization server.

In our case, the user will be the one that will be using our application, the client will be our application, and the authorization server will be the GitHub server.

In the application, the user needs access to make queries, and create schemas and tables in our database, to do that that they will ask the custom function for a login URL, we will then redirect them to the GitHub login page, and after they log in, they will be redirected back to our application with a token that we can use to authenticate them. But we will talk about it in more detail later on.

For now, we need to tell GitHub that we have an app that will be requesting access to their oAuth server. We do that by going into the GitHub Developer Settings in the “OAuth Apps” section and creating a new application.

The important part is that we put a correct callback URL. For us, the callback URL will be http://localhost:9926/oauth/callback, since we are using the default port for the custom function API.

Once you created the app, you will be redirected to a page with the app information, this page will have a Client ID and a Client Secret. We will need those later on.

Now that we have our GitHub app created, we can start creating our custom functions.

HarperDB oAuth Custom Functions

The OAuth flow requires a few steps, we would need to create all those steps in order to make our user authenticated. But, fortunately, the HarperDB team has a nice add-on called HarperDB cf-auth-oauth2 that makes life a lot easier for us.

To “install” a custom function is to basically copy the whole directory structure to another directory inside the custom_functions directory that we linked with our Docker container. If you have already started your docker container, you’ll see there’s a new directory in your execution location called harperdb, inside it you’ll see a custom_functions directory, that’s the one we need to copy our custom functions to.

First, if you have any directories inside the custom_functions directory, delete them. We are going to clone the repository inside the custom_functions directory, so we don’t want any other directories there.

Let’s clone the repository using git:

git clone https://github.com/khaosdoctor/cf-auth-oauth2.git oauth

Note that we are cloning the repository to a directory called oauth, this is important because the directory name will be the name of the custom function. And custom functions are called using the directory name as the function name in the URL. This means that our custom function entrypoint will be http://localhost:9926/oauth.

After cloning, let's open a terminal, go into the directory using cd ./oauth and run npm install to install all the dependencies.

If you go back to the HarperDB Studio, you’ll see that the custom function is already there, if it doesn’t appear, just click the “refresh” button.

We don’t need to use the online editor to create or edit the function files, we can just use our favorite text editor. I’ll be using VSCode for this example.

First, we need to rename the .authConfig.json.example to .authConfig.json, it’ll look like this:

{
  "client": {
    "id": "1234",
    "secret": "12234"
  },
  "provider": "GITHUB_CONFIGURATION",
  "loginPath": "/login/github",
  "callback": "http://localhost:9926/oauth/callback",
  "schema": "hdb_auth",
  "table": "sessions",
  "salt": "input here a random string",
  "logout": "/logout"
}

We need to fill the client.id and client.secret with the values we got from the GitHub app we created earlier. The provider is the name of the provider we are using, in our case, it’s GitHub. The loginPath is the path that we will use to redirect the user to the GitHub login page. The callback is the URL that GitHub will redirect the user to after they log in. The schema and table are the schema and table names that we will use to store the user sessions. The salt is a random string that we will use to encrypt the user session. And the logout is the path that we will use to log the user out.

To get the client id and client secret, we need to go back to the GitHub app page and copy the values from there. The id will be already presented to you, but the client secret needs to be created by clicking the “Generate a new client secret” button.

Make sure to copy the client secret right after you click the button because GitHub won’t show it to you again.

After we have the client id and client secret, we can fill the .authConfig.json file with the values from it, and also the salt field.

This will be a random string, you can use any string you want, but make sure it’s a fairly long one with numbers, symbols, and letters.

{
  "client": {
  "id": "the ID",
    "secret": "the secret"
  },
  "provider": "GITHUB_CONFIGURATION",
  "loginPath": "/login/github",
  "callback": "http://localhost:9926/oauth/callback",
  "schema": "hdb_auth",
  "table": "sessions",
  "salt": "384yr3#$%@$AFUHUDvWERHGahduvhsdf",
  "logout": "/logout"
}

After the configuration file is created and saved, we can go back to our studio page and click the “Restart server” button. This will reload the code and apply our changes.

How to use the custom function

This add-on works by supplying you with a few endpoints that you can use to authenticate the user. The first of them is the setup path, which is the path we will call to create the session schema and tables. We can just make a GET request to http://localhost:9926/oauth/setup and it will create the schema and tables for us. You can see the created tables in the “Browser” tab of the studio:

The next endpoint is the login path, which is the path we will use to redirect the user to the GitHub login page. For this example, we will be just using the API, we won’t have any static website or UI, so you can just open your favorite browser and navigate to http://localhost:9926/oauth/login/github.

You’ll be redirected to GitHub’s login or permissions page, where you can log in with your GitHub account or, if you’re already logged in, you can grant the permissions to the app.

Once you give the app the permissions, you’ll automatically be redirected to the callback URL that we configured earlier. This will create a session for the user and store it in our HarperDB session schema.

For each new login, we’ll be creating a new user ID and a new session token automatically, which means that every time you access that URL, you’ll be creating a new session. This is not ideal, but it’s the easiest way to implement it for this example.

You’ll also receive a simple response with a token that looks like this:

some-token.some-token

This is our user token, the first bit of it is the user ID, and the second bit is the session token. We can use this token to authenticate the user in our HarperDB instance.

Manipulating the database

Now our user is already able to perform queries in the instance. Fortunately, the add-on already has some endpoints that we can use to create a schema and a table, and insert data into it.

Let’s create a new table called breeds in a schema called dogs. For that we can do a GET request first to http://localhost:9926/oauth/create/schema/dogs using the user token in the Authorization header with the harperdb token type, like this:

GET /oauth/create/schema/dogs HTTP/1.1
Authorization: harperdb token.goeshere
Host: localhost:9926

You can also use a request tester like Postman, Insomnia, or Thunder Client, or simply use the curl command:

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

This will create a new schema called dogs in our HarperDB instance.

After that, we can create a new table called breeds in the dogs schema, using the same method, but to a different endpoint, we’ll use http://localhost:9926/oauth/create/table/dogs/breeds:

GET /oauth/create/table/dogs/breeds HTTP/1.1
Authorization: harperdb token.goeshere
Host: localhost:9926

You can also use a request tester like Postman, Insomnia, or Thunder Client, or simply use the curl command:

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

Now we have a table in our instance as well.

Creating data

To insert data into the breeds table we just need to perform a POST request to http://localhost:9926/oauth/dogs/breeds with an object or an array of objects that will be our inserted data, let’s create two breeds with the following JSON:

[
  {
    "name": "Labrador Retriever"
  },
  {
    "name": "German Shepherd"
  }
]

We can do this request using the same method we used to create the schema and table, but this time we’ll use the POST method and we’ll send the data in the body of the request.

POST /oauth/dogs/breeds HTTP/1.1
Authorization: harperdb token.goeshere
Content-Type: application/json
Host: localhost:9926
Content-Length: 83

[
  {
    "name": "Labrador Retriever"
  },
  {
    "name": "German Shepherd"
  }
]

You can also use a request tester like Postman, Insomnia, or Thunder Client, or simply use the curl command:

curl -X POST \
  '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 two breeds in our table.

Reading data

To read data from the breeds table we just need to perform a GET request to http://localhost:9926/oauth/dogs/breeds/:id where the ID is the breed ID we want to read. Let’s read the breed with the ID 3e57f916-79ef-4d96-93f9-da35dd84dd87.

For that we make a request like this:

curl -X GET \
  'http://localhost:9926/oauth/dogs/breeds/3e57f916-79ef-4d96-93f9-da35dd84dd87' \
  --header 'Authorization: harperdb token.goeshere'

And get the following data:

[
  {
    "__createdtime__": 1671293628332,
    "__updatedtime__": 1671293628332,
    "id": "3e57f916-79ef-4d96-93f9-da35dd84dd87",
    "name": "German Shepherd"
  }
]

Logging out

To log out a user, we can just use the GET /oauth/logout endpoint, that will remove all the user sessions from the database that are related to that user ID.

Conclusion

While this is a simple example, it highlights what we can do with the power of the custom functions and oAuth together.

I encourage you to create on top of this idea to implement even more oAuth strategies and log users using third-party authentication to make your apps more secure than ever using HarperDB.

All Blogs
Laptop showing HarperDB Studio

Get Started With HarperDB

Learning from the experts.

Receive once monthly updates with the latest tutorials, podcasts, articles, and more from HarperDB.
Thank you! Your submission has been received!
Please fill in all fields, to appease the coding gods.