# Securing Search UI with authentication and authorization

Authentication and Authorization rules are THE MOST IMPORTANT security considerations when building a production grade app, yet they are often left out as a second thought. In this post, we will be building authentication (Who are you?) and authorization (Are you allowed to see this?) flows to a [Airbnb search UI](https://blog.reactivesearch.io/geo-search-with-react-and-elasticsearch). It shows listings of available stays where users can search and filter according to price, no. of guests, and date ranges. We are going to extend it, to include authentication and authorization.

Our extended app would have a login mechanism that would allow users to sign up/sign in. Each user would have a role assigned to them, either `guest` or `host`. As a `host` they would be able to create a listing but not as a `guest`. They would also be able to switch roles to test both types.

The key components that we will be using here are:

- [ReactiveSearch](https://www.reactivesearch.io/product/search-ui), a data-driven UI components library for React for building the UI views
- [Netlify functions](https://www.netlify.com/products/functions/), server-side functions that act as API endpoints
- [Auth0](https://auth0.com), as a hosted authentication service
- [ReactiveSearch.io](https://www.reactivesearch.io/product/search) as a hosted search service

You can play with the [final search UI over here](https://search-with-auth.netlify.app/).

# Definitions

### Authentication v/s Authorization

Authentication and authorization are complementary to each other and they solve two different problems:

> Is the user who they claim they are? — Authentication

> Is this user allowed to perform this action? — Authorization

### RBAC

After the authentication happens, we now want to give permissions to the users to access certain parts of the app and do certain things. We use Auth0 which implements authorization using RBAC.

Role-based access control (RBAC) restricts activities a certain user can perform. Traditionally, we used to assign permissions directly to users. In RBAC, we assign permissions to roles and then roles to users. It offers a simple and manageable approach to access management that is less prone to error than assigning permissions to users individually.

# Before we begin

### Setting Auth0

To set up Auth0 we would need to perform the below steps. You can use the [Auth0 guide](https://auth0.com/docs/get-started/applications/application-settings) for reference.

1. Sign up/Sign in to [Auth0 dashboard.](https://auth0.com/)

2. [Under the Applications tab, click on create application and choose a single page application(SPA).](https://auth0.com/docs/get-started/applications). This would be used by our frontend inside the [`Auth0Provider`](https://github.com/auth0/auth0-react#getting-started) component of `@auth0/auth0-react`
   ![auth0 applications](https://cdn.hashnode.com/res/hashnode/image/upload/v1654079309710/V2zfrWSRC.png align="left")

3. We would need to add some URLs to the Auth0 SPA app you created above, which our frontend application would be running on and making authentication requests. Leave `Application URI` blank and add `http://localhost:3000, http://localhost:8888` in the rest of the fields for now. You can also add more URLs, separating them by a comma.
   ![Application configuration](https://cdn.hashnode.com/res/hashnode/image/upload/v1654080238916/D07WgDNiW.png align="left")

4. Create a new machine-to-machine (M2M) application. This would be similar to creating a SPA application from the **Applications** tab but you have to choose M2M instead. This would be used inside of netlify functions and corresponds to server-side code.

5. Since we would be managing Auth0 roles and users from our server, we would need to authorize the machine-to-machine application in the Auth0 Management API.
   ![Authorize M2M application](https://cdn.hashnode.com/res/hashnode/image/upload/v1654081200389/CIZmz4tdw.png align="left")

# Building the app

## Step 1: Cloning the airbeds-app

We have already built this app in a [previous blog post](https://blog.reactivesearch.io/geo-search-with-react-and-elasticsearch). You can clone the code from [the repository](https://github.com/appbaseio-apps/Airbeds-app). You can run the below commands.

```sh
git clone https://github.com/appbaseio-apps/Airbeds-app
yarn
yarn start
```

## Step 2: Configuring Auth0 and adding login/logout

We want to extend the **Airbeds app** by adding a login inside of the app. We would first install the [`@auth0/auth0-react`](https://www.npmjs.com/package/@auth0/auth0-react) library.

```sh
yarn add @auth0/auth0-react
```

Then we can wrap the whole app inside the `Auth0Provider` component. Add the credentials from the **Auth0 SPA app** we created above.

```jsx
//index.js
<Auth0Provider
  domain="{YOUR_DOMAIN}"
  clientId="{YOUR_CLIENT_ID}"
  redirectUri={window.location.origin}
>
  <App />
</Auth0Provider>
```

Then we would make a login button by using the `useAuth0` hook. That's it. We would now have a working login.

```jsx
//LoginButton.js
import { useAuth0 } from "@auth0/auth0-react";

const LoginButton = (props) => {
  const { loginWithRedirect } = useAuth0();

  return (
    <button onClick={() => loginWithRedirect()} {...props}>
      Log In
    </button>
  );
};
```

## Step 3: Making UI components

This app is going to be a little complex and we would try to mitigate complexity by making the UI components first and then building the logic.

### Step 3a: Switch roles component

In order to change roles, we would need a modal to display the available roles and allow users to select them. For this would create a component in [`SwitchRoleModal.js`](https://github.com/appbaseio-apps/search-with-authorization/blob/master/src/components/RoleSwitchModal.js).

<iframe src="https://codesandbox.io/embed/github/appbaseio-apps/search-with-authorization/tree/step-3/switch-roles-modal?fontsize=14&hidenavigation=1&theme=dark&view=preview"
     style="width:100%; height:500px; border:0; border-radius: 4px; overflow:hidden;"
     title="demo"
     allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking"
     sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"
   ></iframe>

### Step 3b: Listing form component

We want the users to create an entry similar to airbeds. The form would have several fields and validation. The form component would also validate before submitting data. Things we need to validate are the text field shouldn't be empty, number fields shouldn't be negative, and "Date to" should be less than "Date from". We would make a separate file [`ListingForm.js`](https://github.com/appbaseio-apps/search-with-authorization/blob/master/src/components/ListingForm.js) for the purpose.

<iframe src="https://codesandbox.io/embed/github/appbaseio-apps/search-with-authorization/tree/step-3/listing-form?fontsize=14&hidenavigation=1&theme=dark&view=preview"
     style="width:100%; height:500px; border:0; border-radius: 4px; overflow:hidden;"
     title="demo"
     allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking"
     sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"
   ></iframe>

## Step 4: Netlify functions

We would be making netlify functions to run our server-side logic.

Netlify functions live in a directory under `/netlify/functions`, so netlify can detect it. All of the functions we create below would be added to that folder.

### Step 4a: Function for getting current user's role

We would be creating a function for getting the user's role in a file `/netlify/functions/getRole.js`.

Here we are going to use the `auth0` library's Management API to get roles and various other things.

```
npm i auth0
```

Remember the Auth0 M2M application we made above. Copy the credentials and make a `.env` file with variables `AUTH0_DOMAIN`, `AUTH0_CLIENT_ID`, `AUTH0_CLIENT_SECRET`, filling the respective variables. Now we can use the management API by configuring it as below.

```js
//getRole.js
const ManagementClient = require("auth0").ManagementClient;

const auth0 = new ManagementClient({
  domain: process.env.AUTH0_DOMAIN,
  clientId: process.env.AUTH0_CLIENT_ID,
  clientSecret: process.env.AUTH0_CLIENT_SECRET,
});
```

We would be serving a **GET** request which would send `user_id` as a query parameter. We can get it from the `event` object and then use it in `getUserRoles` to get roles corresponding to the `user_id`. We would have to expose a method `handler` on `exports`, so netlify can call it with `event` and `context`. Note that, netlify functions don't allow supplying a body in a **GET** request.

```js
//getRole.js
exports.handler = async function (event, context) {
  try {
    const { user_id } = event.queryStringParameters;
    const roles = await auth0.getUserRoles({ id: user_id });
    return {
      statusCode: 200,
      body: JSON.stringify(roles[0] ? roles[0] : {}),
    };
  } catch (error) {
    console.log({ error });
    return {
      statusCode: 500,
      body: JSON.stringify({ error: error.message }),
    };
  }
};
```

### Step 4b: Function for assigning roles.

In a file `assignRole.js`, we would make a function for assigning roles. We would get `user_id` and `role` to be assigned inside the body of the request. We can then use it to `assignRolesToUser`. A role object contains `name`, `id`, etc., but we only need to send an array of role ids. So we use `.map` as `matchedRoles.map((role) => role.id)`.

```js
//assignRole.js
exports.handler = async function (event, context) {
  const params = JSON.parse(event.body);
  //get all the roles which can be assigned to a user
  const allRoles = await auth0.getRoles();
  //Remove all the roles from a user
  await auth0.removeRolesFromUser(
    { id: params.user_id },
    { roles: allRoles.map((role) => role.id) }
  );
  //Find a role name that matches the one sent in request.
  const matchedRoles = allRoles.filter((role) => role.name === params.role);
  const roles = await auth0.assignRolestoUser(
    {
      id: params.user_id,
    },
    { roles: matchedRoles.map((role) => role.id) }
  );
  return {
    statusCode: 200,
    body: JSON.stringify(roles),
  };
};
```

### Step 4c: Function for creating a listing

We get data in Airbeds-app from an Appbase index. We would want to create a listing inside that index, so it becomes searchable with all other entries. For this purpose, we are going to use `appbase-js` which allows making requests to the Appbase index to create a listing. This endpoint `createListing` would receive data from a form for the listing and would send it through `appbase-js`. We would require API credentials with write access for this.

```js
//createListing.js
exports.handler = async function (event, context) {
  const { user_id, ...body } = JSON.parse(event.body);
  const roles = await auth0.getUserRoles({ id: user_id });
  const isHost = roles.filter((role) => role.name === "host").length > 0;
  if (isHost) {
    const appbaseRef = Appbase({
      url: "{YOUR APPBASE CLUSTER URL}",
      app: "{YOUR INDEX/APP name}",
      credentials: "{APPBASE API CREDENTIAL}",
    });
    const doc = await appbaseRef.index({
      body,
    });
    return {
      statusCode: 200,
      body: JSON.stringify(doc),
    };
  } else {
    throw new Error(
      "Your listing couldn't be created as your role is guest. Become a host to create the listing."
    );
  }
};
```

### Step 5: Connecting UI with the functions

We have our UI components and functions. We just need to bring them together.

All netlify functions get converted into an endpoint so we can use them on the frontend. They are available on `/.netlify/functions`.

Eg. In our `ListingForm.js` component we would need to submit data to `/.netlify/functions/createListing`.

```js
//ListingForm.js
fetch("/.netlify/functions/createListing", {
  method: "POST",
  body: JSON.stringify({
    name: "Hemmingway heights",
    date_to: "02-02-2020",
    date_from: "02-03-2020",
    bedrooms: 2,
    accomodates: 4,
    price: 320,
    host_name: "Mansingh",
    listing_url: "https://www.airbnb.com/rooms/1781873",
    image:
      "https://a1.muscache.com/im/pictures/25123883/1d7c2b2e_original.jpg?aki_policy=medium",
    has_availability: true,
    beds: 2,
    bed_type: "Real bed",
    location: {
      lon: -122.36470165349674,
      lat: 47.667596803320116,
    },
  }),
});
```

```js
//ListingForm.js
fetch("/.netlify/functions/createListing", {
  method: "POST",
  body: JSON.stringify({
    ...body,
    //Also send the user_id from auth0. We can get this from "user" in "useAuth0"
    user_id: user.sub,
  }),
});
```

For building the app locally we would use `netlify-cli`. We can install it using the below command.

```sh
npm i -g netlify-cli
```

We can then run

```sh
netlify dev
```

Congratulations! We added a fully functional authentication and authorization mechanism to an existing app. This is incredible. [You can look at the final app here.](https://search-with-auth.netlify.app/).

# Summary

Here comes the end of our journey, and we have extended our Airbeds search UI to have RBAC. Our users can now create listings if they have the correct role(host) selected. To summarize everything so far:

This post builds upon the Airbeds search UI to include authentication and authorization. We authenticate the user using a login and then on the basis of their assigned roles, allow them access to create a listing in the index(authorization).

- **Step 1:** [Cloning the airbeds search UI](https://github.com/appbaseio-apps/search-with-authorization/tree/step-1/cloning-airbeds)
- **Step 2:** [Configuring Auth0 and login/logout](https://github.com/appbaseio-apps/search-with-authorization/tree/step-2/configuring-login-and-logout)
- **Step 3:** [Make UI components](https://github.com/appbaseio-apps/search-with-authorization/tree/step-3/listing-form)
- **Step: 4** [Netlify functions](https://github.com/appbaseio-apps/search-with-authorization/tree/step-4/functions)
- **Step: 5** [Connecting UI with the functions](https://github.com/appbaseio-apps/search-with-authorization/tree/step-5/connect-ui)

