DEV Community

Cover image for Quickly add role-based access control (RBAC) to a blog dashboard in Nuxt.js
Moronfolu Olufunke for Hackmamba

Posted on • Updated on

Quickly add role-based access control (RBAC) to a blog dashboard in Nuxt.js

Role-based access control (RBAC) is a form of system security that restricts unauthorized people from gaining access to some system’s data. It helps to protect not only data, but it also separate provides users the data needed to get their work done.

RBAC for an entry-level employee or intern could mean restricting access to any of the company’s services that could be potentially at risk if not handled properly.

Appwrite is a self-hosted backend-as-a-service platform that provides developers with all the core APIs required to build any application. Appwrite delivers solutions that help building backend servers for applications.

This article discusses adding RBAC to a blog dashboard in Nuxt.js using Appwrite.

GitHub

Check out the complete source code here.

Prerequisites

Understanding this article requires the following:

  • Installation of Node.js
  • Basic knowledge of JavaScript
  • Docker installation
  • An Appwrite instance; check out this article on how to set up an instance locally or via one-click install on DigitalOcean or Gitpod

Creating our project

Use npx create-nuxt-app <project-name> to create a new Nuxt.js project.
The process of scaffolding the project provides a list of options, which should look like this:

Nuxt.js create app options

We can start our Nuxt3 application by running the following command:

cd <project name>
npm run dev
Enter fullscreen mode Exit fullscreen mode

Nuxt.js will, in turn, start a hot-reloading development environment that is accessible by default at http://localhost:3000.

Adding the blog dashboard template

To add RBAC to an application, we will grab an off-the-shelf dashboard that we will modify to meet our goal. Our off-the-shelf template will be the dashboard example from the Bootstrap website.

By the time we are through with customization, the dashboard should look like the image below.

The dashboard template

We can follow the progress by switching to the starter-app branch in this GitHub repository.

Installing Appwrite

To use Appwrite in our project, we will install the Appwrite SDK from the command line, like so:

npm install appwrite
Enter fullscreen mode Exit fullscreen mode

Creating an Appwrite project

To create a new project, we will start up the Appwrite instance on our machine and navigate to the specified hostname and port http://localhost:80. Next, we need to log into our account or create an account if we don’t have one.

Appwrite sign up page

Learn more about setting up Appwrite here. On the console, click on the Create Project button.

!Appwrite project creation page](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/enjfmgfj9sih7gz0ekrc.png)

The project dashboard becomes visible on the console after the project creation. We can access the inner page from the Settings tab at the bottom of the page.

Appwrite project setting

The inner page allows us to copy the Project ID and API Endpoint for our Nuxt application.

Appwrite project overview

We will then proceed to create an init.js file in our Nuxt.js application’s root directory to initialize the Appwrite Web SDK by adding the code block below:

import { Client, Account } from "appwrite";
export const client = new Client();
export const account = new Account(client);
client
    .setEndpoint('http://localhost/v1') // API Endpoint
    .setProject('***') // project ID
;
Enter fullscreen mode Exit fullscreen mode

Creating the signup page

We will create a signup page where people who will later be assigned roles to perform certain operations within the database can generically create an account. To do this, we will create a components/Signup.vue and add the code below:

<template>
  <div class="">
    <main class="form-signin w-100 mx-auto py-5 px-5 my-5">
      <form class="mt-5" @submit.prevent="signUp">  
        <h1 class="h3 mb-4 fw-normal text-center">Admin Dashboard</h1>
        <div class="form-floating mb-3">
          <label for="floatingInput">Username</label>
          <input type="username" v-model="username" class="form-control" id="floatingInput" placeholder="Username">
        </div>
        <div class="form-floating mb-3">
          <label for="floatingInput">Email address</label>
          <input type="email" v-model="email" class="form-control" id="floatingInput" placeholder="name@example.com">
        </div>
        <div class="form-floating mb-4">
          <label for="floatingPassword">Password</label>
          <input type="password" v-model="password" class="form-control" id="floatingPassword" placeholder="Password">
        </div>
        <div class="form-floating mb-4">
          <label for="floatingPassword">Confirm Password</label>
          <input type="confirm-password" v-model="confirmPassword" class="form-control" id="floatingPassword" placeholder="Confirm Password">
        </div>

        <button class="w-100 btn btn-lg btn-primary" type="submit">Get Access</button>
        <p class="mt-3">Already have an account? <nuxt-link to="/signin">Sign in</nuxt-link> </p>
      </form>
    </main>
  </div>
</template>
<script>
export default {
  data: () => ({
    username: "",
    email: "",
    password: "",
    confirmPassword: ""
  }),
}
</script>

<style scoped>
.form-signin {
  max-width: 400px;
  padding: 15px;
  border: 5px solid #0069D9;
}
.form-signin .form-floating:focus-within {
  z-index: 2;
}
.form-signin input[type="email"] {
  margin-bottom: -1px;
  border-bottom-right-radius: 0;
  border-bottom-left-radius: 0;
}
.form-signin input[type="password"] {
  margin-bottom: 10px;
  border-top-left-radius: 0;
  border-top-right-radius: 0;
}
</style>
Enter fullscreen mode Exit fullscreen mode

Implementing the code block above, we achieved the following:

  • Created a functional form to handle input from potential users of our dashboard
  • Added the data in the Vue script to manage the username, email, and password of the people who will fill out the form

Adding the signup functionality

Our interface will need to be connected to Appwrite to authenticate users. To do this, we will add the following code in the <script> tag of our components/Signup.vue file.

<script>
import {account} from '~/init'
export default {
  data: () => ({
    // data goes here
  }),
  methods: {
    signUp: async function(){
      if (this.password.length >= 8){
        if(this.password === this.confirmPassword) {
          try{
            await account.create('unique()', this.email, this.password, this.username)
            alert("account created successfully")
            window.location.href = '/signin'
          } catch (e) {
            console.log(e)
          }
        } else {
          alert("password do not match")
        }
      } else {
        alert("password length should be up to 8 characters")
      }
    },
  }
}
</script>
Enter fullscreen mode Exit fullscreen mode

The code above does the following:

  • Imports the Appwrite SDK initialized in the init.js file.
  • Creates the signUp function in the methods property, which performs the following functions:
    • Verifies that the password length is equal to or greater than eight characters.
    • Confirms that the characters in password and confirmPassword are the same.
    • Accesses Appwrite’s services using the account.create to create a new account using the user’s email, password, and name.
      • To use Appwrite’s create account service, it’s mandatory to add the userId; in this case, we used uniqueId to generate one automatically.
      • Adding email and password is also mandatory.
      • The name option is optional, and we can create an account without one.
    • window.location.href allowed us to navigate to the chat interface after successful account creation.

Next, let's import the components/Signup.vue into the pages/index.vue, like so:

<template>
  <div>
    <Signup/>
  </div>
</template>
Enter fullscreen mode Exit fullscreen mode

At this point, our signup page should look like the below:

The signup page

Creating the sign-in interface

We also need to create a sign-in page that allows already registered users to log in to the dashboard. To do this, we will create a pages/Signin.vue and add the code below:

<template>
  <div class="">
    <main class="form-signin w-100 mx-auto py-5 px-5 my-5">
      <form class="mt-5" @submit.prevent="signIn">  
        <h1 class="h3 mb-4 fw-normal text-center">Admin Dashboard</h1>
        <div class="form-floating mb-3">
          <label for="floatingInput">Email address</label>
          <input type="email" v-model="email" class="form-control" id="floatingInput" placeholder="name@example.com">
        </div>
        <div class="form-floating mb-4">
          <label for="floatingPassword">Password</label>
          <input type="password" v-model="password" class="form-control" id="floatingPassword" placeholder="Password">
        </div>
        <button class="w-100 btn btn-lg btn-primary" type="submit">Get Access</button>
        <p class="mt-3">New? Create an account? <nuxt-link to="/">Sign up</nuxt-link> </p>    
      </form>
    </main>  
  </div>
</template>
<script>
import {account} from '~/init'
export default {
  data: () => ({
    email: "",
    password: "",
  }),
}
</script>
Enter fullscreen mode Exit fullscreen mode

The code block above achieves the following:

  • Creates a functional form to handle input from registered users
  • Adds the data in the Vue script to manage the email and password ### Adding the sign-in functionality Our interface will need to be connected to Appwrite to authenticate users for signing in. To do this, we will add the code below in the SigninView.vue.
<script>
import {account} from '~/init'
export default {
  data: () => ({
    email: "",
    password: "",
  }),
  methods: {
    signIn: async function () {
      try{
        let accountDetails = await account.createEmailSession(this.email, this.password)
        alert("user signed in")
        this.$router.push({ path: `/dashboard/${accountDetails.userId}`})
      } catch (e){
        console.log(e)
      }
    },
  }
}
</script>
Enter fullscreen mode Exit fullscreen mode

Above, we imported the Appwrite SDK initialized in the init.js file. We also created a function called signIn that does the following:

  • Accesses Appwrite’s services using the account.createEmailSession to create a new account session using the user’s email and password
  • this.$router.push allows us to navigate to the chat interface after successful user login

At this point, our interface will look like this:

The sign-in interface

Adding user role on the Appwrite dashboard

Appwrite allows us to give users role-based access to perform either or all of these functions:

  • Read
  • Read and write
  • Read, write, and delete

To do this, we will create three users from our signup page and give them different role access from Appwrite.

Creating users

We will create one user here for demo purposes to show us how the signup process works.

Creating users

We will do this for two more users. Afterward, we will navigate to our appwrite dashboard to add the permissions for each user.

Setting up Appwrite’s database

One of the services offered by Appwrite is the Database Service, which allows us to store data and fetch it as needed.

Creating a database

To create a database in Appwrite, click on the Database option in the menu tab, where we can add a database to the system.

Creating a database

Creating a collection

Appwrite’s database services also provide collection services. These collections act as containers for documents. To create a collection, select the desired database from which we can add a new collection.

Creating a collection

After creating a new collection, the system redirects us to the Settings page, where we can set the top-level permissions needed to access the database service. In our case, we will allow document security, allowing users to access only the documents they have express permission to access.

Collection dashboard

Creating attributes

Our document will have some peculiar attributes. To create them, we will navigate to the Attributes section. In our case, we will choose the New String Attribute.

Creating attributes

We will also add an Attribute Key named “message” with a size of 256 bits.

Next, we will create a document from where we can add the document permissions.

creating document

From the newly created document, we can set roles and permissions.
We will click on the permissions tab, and in the Role input field, we will pick the role that best describes to whom we are giving access. In our case, the role is select users, as this allows us to assign roles to only specific users. We will match this role with the corresponding user id from some of the already-created users in our database.

Setting roles and permissions

We will create three different roles, and at the end, we should have our documents permission looking like the below:

Documents permission

Connecting the dashboard to Appwrite’s database

We will navigate to the dashboard.vue file and import the Appwrite’s SDK initialized in the init.js and Appwrite’s database method. We will also create an instance of databases using the imported databases method from Appwrite, which takes the client as a parameter. Like so:

import {client} from '~/init'
import { Databases } from 'appwrite';
const databases = new Databases(client);
Enter fullscreen mode Exit fullscreen mode

We will also update the data property in the script tag with the following options that will add state changes to the dashboard. Like so:

data() {
  return {
    permisssionsArray: [],
    denyActive: false,
    dashboardActive: false,
    orderActive: false,
    reportActive: false,
  }
}
Enter fullscreen mode Exit fullscreen mode

Get documents permissions from the database

Now, we can use the getDocumentPermissions function to get document permission:

mounted(){
  this.getDocumentPermissions()
},
methods: {
  getDocumentPermissions: async function() {
    let promise = await databases.getDocument('6381e4342875d287ab7b', '6381e44a94d7c36be8f9', '6385d5b030814a0a9b78');
    const match = promise.$permissions.find(element => {
      if (element.includes(this.$route.params.dashboard)) {
        this.permisssionsArray.push(element)
        let newPermission = ''
        for (let i = 0; i< this.permisssionsArray.length; i++){
          newPermission += this.permisssionsArray[i]
        }

        if(newPermission.includes('read') && newPermission.includes('update') && newPermission.includes('delete')){
          this.reportActive = true;
        } else if(newPermission.includes('read') && newPermission.includes('update')){
          this.orderActive = true;
          this.dashboardActive = true;
        } else if(newPermission.includes('read')){
          this.dashboardActive = true;
        } 
      }
    });
  },
},
Enter fullscreen mode Exit fullscreen mode

We achieved the following from the above code block:

  • Created the getDocumentPermissions method, which accesses the databases method from Appwrite to get all documents in the database.
  • The getDocumentPermissions method takes the database, collection, and Document ID as parameters. These IDs can be found on the document dashboard on Appwrite.
  • Returned documents from the Appwrite database are stored in the promise variable.
  • Checked whether the user ID in the permissions object matches the current user. If this is true, we create a permissionsAray that contains all the permission for that user.
  • Looped through the array to concatenate the user permissions for easy identification of the proper permissions for interface access.
  • Created conditions based on the user’s permission.

At this point, a user with read access permission can only see the dashboard navigation.

Users with only the read access

A user with read and update permissions can see the dashboard navigation and the navigation links to check orders.

User with the read and update access

Likewise, a user with the read, update, and delete permissions will be able to see the dashboard, the order reports, and the saved reports. Like so:

User with read, update and delete access

Conclusion

This article discusses adding role-based access control to a dashboard using Appwrite’s database and permissions features.

Resources

Latest comments (1)

Collapse
 
kissu profile image
Konstantin BIFERT

Damn, quite extensive tutorial thanks! 🙏🏻
Is there a reason why you did not used Nuxt3 here? Did you faced any specific issues with it or you are just more comfortable with Nuxt2?