Build an End-To-End Encrypted Chat App in Nuxt.js: Messages and Encryption
Moronfolu Olufunke
Posted on October 24, 2022
Encryption involves converting information into secret codes to protect the information’s precise details from being readable to unauthorized people, including cyber-criminals.
This article discusses building an end-to-end encrypted chat application in Nuxt.js, using Appwrite to provide the database service and Cryptr for encrypting the exchanged data.
GitHub
Check out the complete source code here.
Prerequisites
Understanding this article requires the following:
- Installation of Node.js
- Basic knowledge of TypeScript
- Docker installation
- An Appwrite instance; check out this article on how to set up an instance
Moreover, this article is the second part of a two-part series on building an end-to-end encrypted chat app in Nuxt.js. The first part of the article that discusses Setup and Authentication can be found here.
Setting up Appwrite’s database
Appwrite is an end-to-end backend server that provides services that help abstract away some of the complexity of performing backend tasks. One of the services offered by Appwrite is the Database Service, which allows us to store users’ data and fetch them 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 within its database. 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, from which we can set the permissions needed to use the database service — an empty permission field will prevent the client from accessing the document. In our case, we will add the role access of “any” to allow any role access to the collection. As seen in the image below:
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 can then create an Attribute ID with the name “message” with a size of 256 bits.
Connecting the chat app with Appwrite’s database
We will start by importing Appwrite’s SDK initialized in the init.js
and Appwrite’s database
method. This is done as follows:
import {client} from '~/init'
import { Databases, ID } from 'appwrite';
We will also update the data
property in the script
tag with message
, databaseMessages
, and password
to hold the user’s and database’s messages, like so:
data: () => ({
message: '',
databaseMessages: ['']
}),
Then, we will create an instance of databases
using the imported databases
method from Appwrite, which takes the client as parameters.
const databases = new Databases(client);
Listing messages in the database
We can then create a listMessages
function in the methods
property like so:
listMessages: async function (){
let promise = await databases.listDocuments('database ID', 'collection ID');
this.databaseMessages = promise.documents.map(document => document.message)
},
The listMessages
function in the code block above does the following:
-
listDocuments
method accesses thedatabases
method from Appwrite to list all documents in the database. - The
listDocuments
method takes thedatabase ID
andcollection ID
as parameters. These IDs can be found on the Appwrite dashboard. - Returned messages in the Appwrite database are stored in
this.databaseMessages
.
Sending messages to the database
After the above, we can send messages to the Appwrite database by creating a function called sendMessage
in the methods:
sendMessage: async function(){
try {
await databases.createDocument('632754bf9b28a3d032e7', '632754dccfc24bf0505e', ID.unique(), {'message': this.message})
alert('message sent')
this.message = '';
this.listMessages()
} catch(e){
console.log(e)
}
}
The sendMessage
function in the code block above does the following:
- the
createDocument
method accesses thedatabases
method from Appwrite to create a new document in the database. This method takes thedatabase ID
,collection ID
, Appwrite’sID.unique()
method, and the attribute value(s) as parameters. In this case, our attribute value ismessage
, which we created on the Appwrite admin dashboard. - An alert that confirms successful message sending.
- Clears out the message input field, and we call the
listMessages()
function, which lists all the messages that were sent to the Appwrite’s database
Next, we’ll update our chat application interface to render all the messages sent to the database as follows:
<section v-for="databaseMessage in databaseMessages" :key="JSON.stringify(databaseMessage)">
<p :class="databaseMessage ? ['bg-lightest-blue', 'pa2', 'mv2', 'br3', 'w-60', 'f7'] : ''">
{{databaseMessage}}
</p>
</section>
The code block above loops through the messages returned from the database using Vue’s v-for
directive. Individual messages are then rendered with {{databaseMessage}}
.
Putting it all together, our Pages/Chat.vue
file at this stage will look like this: Follow this GitHub Gist.
Our application interface will also look like the below after sending our first message:
Encryption
We will use Cryptr, which is an encryption and decryption aes-256-gcm module for Node.js to encrypt the messages we will be sending on our chat app.
Start using it by installing it in the project like so:
npm install cryptr
We can then import the Cryptr library and create a new instance of Cryptr in our Pages/Chat.vue
, with the new Cryptr instance containing our Cryptr’s secret key.
const Cryptr = require('cryptr');
const cryptr = new Cryptr('*****');
We want to encrypt the messages we are sending and get back the decrypted version, as the encrypted messages do not have much meaning to the reader. To achieve this, we will modify the sendMessage
function in the Pages/Chat.vue
with the following:
sendMessage: async function(){
const encryptedMessage = cryptr.encrypt(this.message)
try {
await databases.createDocument('632754bf9b28a3d032e7', '632754dccfc24bf0505e', ID.unique(), {'message': encryptedMessage})
} catch(e){
console.log(e)
}
}
The code block above does the following:
- We access the
encrypt
method on the Cryptr library and pass the message being sent as a parameter. Thisencrypt
method encrypts the message and stores it in theencryptedMessage
variable. - The data in the
encryptedMessage
is then passed to Appwrite to be saved in the database.
If we check our Appwrite database, we will see the “How are you?” message we sent encoded with a bunch of numbers and alphabets. We will also see the first message (Hello) — in a human-readable format.
Since humans can’t make sense of the encrypted message, we will need to decrypt the message before it is rendered back in the chat interface. To do this, we will edit the listMessages
function in the Pages/Chat.vue
, like so:
listMessages: async function (){
let promise = await databases.listDocuments('632754bf9b28a3d032e7', '632754dccfc24bf0505e');
this.databaseMessages = promise.documents.map(document => cryptr.decrypt(document.message))
},
We fetched the messages from Appwrite’s database in the above code using the databases.listDocuments
method. We then used Cryptr’s decrypt
method to transform the encrypted message we fetched from the database.
At this point, we have successfully created a chat application with end-to-end encryption to keep our conversations secure.
Our application will look like the below:
Resources
Posted on October 24, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
October 24, 2022