Use Firebase Auth to Manage User Permissions and Enforce Principle of Least Privilege on API Endpoints. Part 2.
Tags: firebase, javascript, security, api
Published: 2023-04-10 11:45:31

In the [part 1](https://dev.to/callezenwaka/user-permissions-and-the-principle-of-least-privilege-on-api-endpoints-using-firebase-part-1-16if) of User Permissions and the Principle of Least Privilege on API Endpoints using Firebase, we explained the need for managing user authentication and authorization using roles and permissions.

We also applied the principle to various server endpoints. Here, we are jumping straight into client implementation using [Vue.js](https://vuejs.org/), An approachable, performant and versatile framework for building web user interfaces.

A quick display of the project tree is depicted below:

```
.
├── README.md
├── index.html
├── package-lock.json
├── package.json
├── public
│   └── vite.svg
├── src
│   ├── App.vue
│   ├── assets
│   │   ├── avatar.png
│   │   └── vue.svg
│   ├── components
│   │   ├── core
│   │   │   └── Header.vue
│   │   ├── generics
│   │   └── user
│   │       ├── User.vue
│   │       ├── UserCard copy.vue
│   │       ├── UserCard.vue
│   │       ├── UserCreate.vue
│   │       ├── UserSkeleton.vue
│   │       ├── UserUpdate.vue
│   │       └── Users.vue
│   ├── config
│   │   └── index.js
│   ├── database
│   │   └── index.js
│   ├── firebase
│   │   └── index.js
│   ├── main.js
│   ├── router
│   │   └── index.js
│   ├── service
│   │   └── index.js
│   ├── store
│   │   ├── index.js
│   │   └── modules
│   │       └── user.js
│   ├── style.css
│   ├── utils
│   │   └── index.js
│   └── views
│       ├── Dashboard.vue
│       ├── Login.vue
│       ├── NotFound.vue
│       └── Register.vue
└── vite.config.js
```
 
Unlike in the server side, the client side is straight forward as most of the heavy lifted has already been performed on the server. However, few directories deserve a mention: `components`, `config`, `firebase`, `store`, and `utils`. 

The component directory has all the various pages for performing `CRUD` operations on the client side. The config directory has `axios` implementation for making requests and getting responses from the server.

```
export const request = async (url, method, token, payload, query) => {
  return await axios({
    method: method,
    url: `${url}`,
    data: payload,
    params: query,
    headers: {
      'Accept': 'application/json',
      'Authorization': `Bearer ${store.getters.idToken}`,
    },
    json: true,
  });
};
```

Then, the firebase directory has the `Firebase configuration object` containing keys and identifiers for our application. 
The store directory has, well, the `Veux` store that manages our data state and help centralize the implementation of requests to API endpoints. We will come back to Veux store shortly.

And finally, the utils directory that has our custom defined functions. Within this file, we defined the `isAuhorised` method that together with firebase `onAuthStateChanged` supports the re-validation of user `token` and `claims`.

```
/**
 * check user authorisation
 * @param {string} action 
 * @returns {boolean}  
 */
export const isAuhorised = (action) => {
  // TODO: get current user permissions
  const { permissions } = store.getters.profile;
  // TODO: verify if user has permission for the intended action
  return permissions.some(permission => permission.name === action && permission.value === true);
};
```

The main.js file serves as the entry point to our client application. Here, we use the `onAuthStateChanged` method from the `firebase/auth ` to identify and setup the current signed in user, authentication status, role and permissions custom claims, and `id token`.

```
onAuthStateChanged(auth, async user => {
  if (user && user.emailVerified) {
    const idTokenResult = await user.getIdTokenResult();
    const { claims: { entity, name, permissions } } = await user.getIdTokenResult();

    if (idTokenResult && idTokenResult.claims) {
      await store.dispatch('setProfile', { ...user, entity, name, permissions });
      await store.dispatch('setClaims', idTokenResult.claims);
      await store.dispatch('setIsAuthenticated', true);
      await store.dispatch('setIdToken', idTokenResult.token);
      await store.dispatch('setCurrentUser', idTokenResult.claims.entity);
    }
  }
});
```

Finally, within the Veux store, we will use `user.js` as the entry point for invoking the user endpoints. To be sure that a user is granted only the necessary access needed to execute a task, we are going to use the `isAuhorised` function. The necessary action is passed as a parameter which either allow or deny requests to the server.

```
if(!isAuhorised(action)) return;
```

So, when a user tries to make an API call to the `getUsers` endpoint, we can check if the user has enough privilege to continue:

```
  async getUsers(context, payload) {
    try {
      // TODO: check user permission
      if(!isAuhorised('readAll')) return;
      // TODO: api call
      await context.dispatch('setLoading', true);
      if (!payload && context.state.users && !!context.state.users.length) {
        await context.dispatch('setLoading', false);
        return context.state.users;
      }
      const { data } = await userApi.getUsers(context.rootGetters.idToken);
      if (!Array.isArray(data)) return;
      await context.dispatch('setLoading', false);
      context.commit('SET_USERS', data);
      return data;
    } catch (error) {
      console.log('Error: ', error);
      return;
    }
  },
```

This way, we are making sure that users do not attempt a request unless they have the permission to do so. The repository for this tutorial is on [GitHub](https://github.com/callezenwaka/demos/tree/master/vue-client).

If you like the article, do like and share with friends.

Reference:
https://www.cyberark.com/what-is/least-privilege/