April 11, 2023

Azure Active Directory Authentication with HarperDB

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

Introduction

Following our series of articles on how to authenticate with HarperDB using different authentication providers, we already saw how to authenticate with oAuth2, then automating the process using Auth0. Now, we will see how to authenticate with Azure Active Directory (AAD).

Azure Active Directory

Azure Active Directory (Azure AD) is a cloud-based identity and access management service from Microsoft that provides a secure way for users to access resources and applications. At its core, Azure AD is an implementation of Active Directory (AD) in the cloud, which is Microsoft’s on-premises directory service that stores information about users, computers, and other resources on a network. Azure AD provides the same functionality as AD, but in the cloud, making it more flexible and accessible.

Active Directory

So, what exactly is Active Directory? It’s a service that IT admins use to manage and organize resources on a network. Basically, it’s a big directory that keeps track of user accounts, computer accounts, and other network resources like printers and file shares. With Active Directory, admins can set policies and permissions for users and computers, which makes it easier to manage who can access what.

The cool thing about Active Directory is that it also supports single sign-on (SSO) authentication. This means that users can log in once and access multiple resources with a single set of credentials. So no more having to remember a different username and password for every application! Plus, Active Directory supports group policies, which can be used to enforce security settings, software deployment, and other configurations across the network.

How it works

So how does Active Directory actually work? When a user logs in to their computer, they send a request to Active Directory to authenticate the credentials.

Active Directory checks the user’s credentials against its database and grants access to the resources that the user is authorized to access. Active Directory also keeps track of the user’s permissions and policies, which can be updated by administrators as needed. With Azure AD, you get the same authentication and access control mechanism in the cloud, which makes it easy to manage access to your cloud resources.

This means that we can delegate not only the authentication but also the authorization of our users to Azure AD, which will be responsible for checking if the user has the necessary permissions to access the resources, like tables, schemas, and which operations they can perform in each resource.

Why use it with HarperDB?

In this tutorial we will be using a HarperDB add-on that will help us to connect with AAD and authenticate our users. The advantage of it is that we can delegate both the authentication and roles to the Active Directory, because we can create our users there and give those users custom roles that will be used to authorize them to access the resources.

The add-on will connect to AAD, get the users’ information, and parse those roles into a set of permissions that HarperDB can understand. This way, we can use the same roles that we created in AAD to authorize our users to access the resources in HarperDB.

Pre-requisites

Again, to avoid repeating myself we are going to use our previous articles’ bases to create our HarperDB instance. For that you can read the “Creating the Infrastructure” section of our first article without any modifications. This should give you a HarperDB instance running on a Docker container.

When you start your Docker compose file using docker compose up -d you should have a HarperDB instance running on port 9925, with custom function enabled on port 9926. Also, the custom functions volume will be created on the local directory, which means you will also have a harperdb folder on your project root. That’s where we will put all the files related to our server.

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

Creating the Azure AD Application

To create the Azure AD application, we will need to access the Azure Portal. If you don’t have an Azure account, you can create one for free at https://azure.microsoft.com/en-us/free/. After creating your account, you can access the Azure Portal at https://portal.azure.com/.

Once you are logged in, you can search for the Azure Active Directory service and click on it. This will take you to the Azure AD dashboard.

In the left menu, click on App registrations and then click on New registration to create a new application.

In the Register an application page, you can give your application a name and a redirect URI. The redirect URI is the URL that Azure AD will redirect the user to after the authentication process is complete. In our case, we will use the URL of our HarperDB custom functions instance, which is http://localhost:9926.

We will call the application hdb-aad-integration and set the redirect URI to http://localhost:9926. Selecting that we will only use a single tenant. Then click on Register.

After creating the application, you will be redirected to the application’s overview page. This is the place you can add logos and other stuff to the application to make it look more professional. But for now, we will just focus on the important stuff.

It’s important to note down the Application (client) ID and the Directory (tenant) ID. We will need them later. So we’ll create a new .env file inside the harperdb directory that was created by docker compose. This file will contain all the environment variables that we will use in our application.

AAD_CLIENT_SECRET=
AAD_CLIENT_ID=
AAD_TENANT_ID=
AAD_REDIRECT_URI=http://localhost:9926

We’ll also need the Client secret. To get it, click on Certificates & secrets in the left menu and then click on New client secret.

Give the secret a name and set the expiration time. Then click on Add.

The portal will redirect you to the Client secrets page, where you can see the secret you just created. Copy the secret value and save it in the .env file.

Note: The secret value will only be shown once. If you lose it, you will have to create a new one.

One last thing that we will need is out AAD Authority URL that we will use to authenticate our users. To get it, click on Overview in the left menu and then on the top bar click on Endpoints.

This will give you several URLs that we will use in our application. The one we are interested in is the base endpoint URL, so you can copy any of the given endpoints and replace the final part so the URL looks like https://login.microsoftonline.com/<tenant-id>. Then save it in the .env file.

AAD_CLIENT_SECRET=client-secret
AAD_CLIENT_ID=client-id
AAD_TENANT_ID=tenant-id
AAD_AUTH_URL=https://login.microsoftonline.com/tenant-id

Create the user roles

Now that we created our application, let’s create the user roles that we will use in our application. To do that, click on App Roles in the left menu and then click on Create app role.

This will open a blade on the right side. There you’ll be prompted to give the role a name, a member type, a value and a description. Let’s create the read role on the breed table of the dogs schema:

To connect with our AAD application, we will use the awesome cf-auth-azuread add-on that’s available in the HarperDB Add-Ons repository on GitHub. This add-on will use the syntax schema.table.permission to define the roles. So, for example, if we want to give the user the read permission on the breed table of the dogs schema, we will use the role dogs.breed.read.

Let’s do the same for the write permission:

Create an AAD user

With our application roles created, we need to create an AAD user to test the application. We’ll create two, one with the dogs.breed.read role and another with the dogs.breed.write role. To do that, open a new portal tab and search for Azure Active Directory. Then click on Users in the left menu and then click on New user.

Our first user will be named Bob, and he will have the dogs.breed.write role. So we’ll set the User name to bob, the First name to Bob and the Last name to Smith.

You can give the user any name you want

Also, set the Password field to be auto generated, click on the Show password checkbox and copy the password. We will need it later.

I created another user called Alice, with the dogs.breed.read role. So I set the User name to alice, the First name to Alice and the Last name to Smith. And repeat the same process you did with Bob.

Now that we have our users created, we need to assign the roles to them. To do that, go back to the Azure Active Directory page and select Enterprise Applications on the left menu. Then click on the application you created earlier.

You will see there’s a Users and groups tab. Click on it and then click on Add user/group.

This will open a new page where you can select a user and assign a role to that user. Let’s select Bob and assign him the dogs.breed.write role.

First select the user:

Then select the role:

Repeat the same process for Alice, but this time assign her the dogs.breed.read role. In the end your application should have two users, Bob and Alice, with the roles dogs.breed.write and dogs.breed.read respectively.

Add API Access

When the AAD application is created, it won’t have total access to the API by default. To permit that go to Azure Portal -> Azure Active Directory -> App Registrations -> Your App -> Expose an API

You’ll need to first create an application ID URI, you just need to click the Generate button and it will be created automatically. Then, go to the scopes section and click on Add a scope.

Then, add a new client application, for that you’ll need the client ID of the application. Just paste it there and click on Add application.

Next up you need to the API permissions. Go to Azure Portal -> Azure Active Directory -> App Registrations -> Your App -> API permissions -> Add a permission -> My APIs -> Your API and add the permissions you want to give to the application.

You should see the following permissions added to your application.

Don’t forget to grant admin consent for the application, otherwise it won’t be able to access the API.

The final step is to grant authentication permissions. Go to Azure Portal -> Azure Active Directory -> App Registrations -> Your App -> Authentication.

Replace temporary passwords

Both users will be created with a temporary password, this temporary password will not allow us to log in to the application. To change the password, open an anonymous browser window and go to https://login.microsoftonline.com. There, input the user email that was created (the one that ends with onmicrosoft.com) and click on Next.

Now input the password that was first generated for the user and click on Sign in. You’ll be redirected to a page where you can change the password. Input the new password and click on Change password.

If you are prompted to include more information, just hit Ask later, we won’t need it now:

You’ll be redirected to the Office 365 dashboard. You can just close that window and repeat the process with Alice.

Creating the HarperDB custom function

Now that we have our AAD application created and our users created, let’s create the HarperDB custom function that will use the AAD authentication add-on. To do that, we will use the cf-auth-azuread add-on.

To create a custom function, make sure your docker-compose.yml file has a volume pointing to /home/harperdb/hdb/custom_functions directory like this:

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

Then, create a new directory inside the harperdb directory called api, this is where we will put our custom functions. Create two directories, one called helpers and another called routes. The helpers directory will contain the authentication helper we will create later, and the routes directory will contain the custom functions that will use the authentication helper.

Create the auth helper

You can either clone the repository or download the single index file that’s needed from here and paste it into a new file called msal.js in a folder called helpers inside the api directory.

Now, inside the api directory, let’s install the msal-node package that we will use to authenticate our users. If you don’t have a package.json file in there, run npm init -y to create one, then run:

npm install msal-node

Your package.json should look like this:

{
  "name": "hdbad",
  "version": "0.0.1",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "Lucas Santos (https://lsantos.dev/)",
  "license": "GPL-3.0",
  "dependencies": {
    "@azure/msal-node": "^1.17.0"
  }
}

This helper exposes a single function called validate that we will use to authenticate the users with a pre-validation step.

Create the routes

Inside the api/routes directory, create a new file called index.js and paste the following code:

const path = require('path')
require('dotenv').config({ path: path.resolve(__dirname, '../.env') })
const aadAuth = require('../helpers/msal')
  
module.exports = async (server, { hdbCore, logger }) => {
  // CREATE A DATA RECORD
  server.route({
    url: '/:schema/:table',
    preValidation: (request, response, next) => aadAuth.validate(request, response, next, hdbCore, logger),
    method: 'POST',
    handler: (request) => {
      const { schema, table } = request.params
      const hasPermission = request.body?.hdb_user?.role?.permission[schema]?.tables[table]?.insert === true
  
      if (!hasPermission) return response.code(403).send('Forbidden')
  
      const { records } = request.body
      request.body = {
        operation: 'insert',
        schema,
        table,
        records,
        hdb_user: request.body.hdb_user
      }
  
      return hdbCore.request(request)
    }
  })
  
  // READ DATA RECORDS
  server.route({
    url: '/:schema/:table/:hash',
    preValidation: (request, response, next) => aadAuth.validate(request, response, next, hdbCore, logger),
    method: 'POST',
    handler: (request, response) => {
      const { schema, table, hash } = request.params
      const hasPermission = request.body?.hdb_user?.role?.permission[schema]?.tables[table]?.read === true
  
      if (!hasPermission) return response.code(403).send('Forbidden')
  
      request.body = {
        operation: 'search_by_hash',
        schema,
        table,
        hash_values: [Number(hash)],
        hdb_user: request.body.hdb_user,
        get_attributes: ['name', 'id']
      }
  
      return hdbCore.request(request)
    }
  })
}

You can check that we are using a check to see if the user has permission to perform the operation. If the user doesn’t have permission, we return a 403 status code.

Create the schema and table

Now the last step is to create the schema and the table that we will use to store the data. To do that, go to the HarperDB Studio and, in the browser tab, create the schema dogs and the table breed.

Testing the application

To test our application, you can open any request tool like Postman or Insomnia. We will use Thunder Client for this tutorial.

Let’s use Bob’s credentials to create a new record. To do that, we will use the POST method to the /dogs/breed endpoint. The authentication is sent on the body of the request.

{
  "username": "bob@yourdomain.onmicrosoft.com",
  "password": "thepassword",
  "records": [
    {
      "id": 1,
      "name": "corgi"
    }
  ]
}


You should get a response like this.

Now, if we try to read it using the POST method but in the /dogs/breed/1 endpoint. We will get a 403 status code.

If we replace Bob’s credentials with Alice’s, we will get a 200 status code.

Conclusion

In this tutorial, we learned how to create a custom function that authenticates users using Azure Active Directory. We also learned how to use the preValidation hook to validate the user before executing the custom function.

Using AAD can be a very interesting way to authenticate users in your HarperDB application. You can use it to authenticate users in your web application, mobile application, or even in your IoT devices.