Quickly add role-based access control (RBAC) to a blog dashboard in Nuxt.js
Moronfolu Olufunke
Posted on December 23, 2022
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:
We can start our Nuxt3 application by running the following command:
cd <project name>
npm run dev
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.
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
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.
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.
The inner page allows us to copy the Project ID and API Endpoint for our Nuxt application.
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
;
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>
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>
The code above does the following:
- Imports the Appwrite SDK initialized in the
init.js
file. - Creates the
signUp
function in themethods
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
andconfirmPassword
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 useduniqueId
to generate one automatically. - Adding
email
andpassword
is also mandatory. - The
name
option is optional, and we can create an account without one.
- To use Appwrite’s create account service, it’s mandatory to add the
-
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>
At this point, our signup page should look like the below:
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>
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 theSigninView.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>
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:
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.
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 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.
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.
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.
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.
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.
We will create three different roles, and at the end, we should have our documents permission looking like the below:
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);
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,
}
}
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;
}
}
});
},
},
We achieved the following from the above code block:
- Created the
getDocumentPermissions
method, which accesses thedatabases
method from Appwrite to get all documents in the database. - The
getDocumentPermissions
method takes thedatabase, collection
, andDocument 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.
A user with read and update permissions can see the dashboard navigation and the navigation links to check orders.
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:
Conclusion
This article discusses adding role-based access control to a dashboard using Appwrite’s database and permissions features.
Resources
Posted on December 23, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
December 23, 2022