DEV Community

Cover image for Build an End-To-End Encrypted Chat App in Nuxt.js: Setup and Authentication
Moronfolu Olufunke for Hackmamba

Posted on • Updated on

Build an End-To-End Encrypted Chat App in Nuxt.js: Setup and Authentication

Chat apps have evolved significantly since they debuted in the 1980s. Despite this evolution over decades, chat apps like TikTok, Facebook, and Telegram remain some of the globally most downloaded applications yearly. With the explosion of messaging applications, there has also been a need for even more encryption on message exchange to prevent unauthorized access to the exchanged data.

This article discusses how to build an end-to-end encrypted chat application with Nuxt.js and Appwrite. This article is the first of a two-part series, discussing the authentication and user account management using the Appwrite account API.

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 locally or via one-click install on DigitalOcean or Gitpod

Creating a Nuxt.js 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 application scaffolding 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.

Installing Appwrite

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

npm install nuxt-appwrite
Enter fullscreen mode Exit fullscreen mode

Creating an Appwrite project

To create a new project, start up the Appwrite instance on your 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.

inner create project page for appwrite

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

Appwrite project dashboard

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

Appwrite settings page

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') // Your API Endpoint
    .setProject('***') // Your project ID
;
Enter fullscreen mode Exit fullscreen mode

Building the signup interface

Building a chat application requires having a signup page for first-time users. To develop our signup page, we will create a components/Signup.vue and add the code below:

<template>
  <div class="ba b--light-blue bw3 vh-100 pv5 ph2 athelas">
    <form @submit.prevent="signUp" class="bg-lightest-blue br2 mw6 w-40-m w-70 w-20-l center pa3 shadow-5">

      <h2 class="ttc tc">Sign up</h2>

      <label for="name" class="db mb1 black-70">Name</label>
      <input name="name" id="name" type="text" class="db mb3 w-100 br3 pa2 ba bw2" placeholder="John Doe" v-model="name">

      <label for="email" class="db mb1 black-70">Email</label>
      <input name="email" id="email" type="email" class="db mb3 w-100 br3 pa2 ba bw2" placeholder="example@email.com" v-model="email">

      <label for="password" class="db mb1 black-70">Password</label>
      <input name="password" id="password" type="password" class="db mb3 w-100 br3 pa2 ba bw2" placeholder="••••••••" v-model="password">

      <label for="confirm-password" class="db mb1 black-70">Confirm Password</label>
      <input name="confirm-password" id="confirm-password" type="password" class="db mb3 w-100 br3 pa2 ba bw2" v-model="confirmPassword" placeholder="••••••••">

      <button type="submit" class="center db ph4 pv2 bg-navy ba br3 white pointer">Sign up</button>

      <p>Already have an account? <a href="" class="black-70 b">Sign in</a> </p>

    </form>    
  </div>
</template>

<script lang="ts">
import Vue from 'vue'

export default Vue.extend({
methods: {
  signUp: async function(){}
}

})
</script>
Enter fullscreen mode Exit fullscreen mode

We will 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:

Signup page

Building the sign-in interface

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

<template>
  <div class="ba b--light-blue bw3 vh-100 pv5 ph2 athelas">
    <form @submit.prevent="signinWithEmailandPassword" class="bg-lightest-blue br2 mw6 w-40-m w-70 w-20-l center pa3 shadow-5 ">

      <h2 class="ttc tc">Sign In</h2>

      <label for="email" class="db mb1 black-70">Email</label>
      <input name="email" id="email" v-model="email" type="email" class="db mb3 w-100 br3 pa2 ba bw2" placeholder="example@email.com">

      <label for="password" class="db mb1 black-70">Password</label>
      <input name="password" id="password" v-model="password" type="password" class="db mb3 w-100 br3 pa2 ba bw2" placeholder="••••••••">

      <button type="submit" class="center db ph4 pv2 bg-navy ba br3 white pointer">Sign in</button>

    </form>    
  </div>
</template>
<script lang="ts">
import Vue from 'vue'
import {signIn} from '~/init'
export default Vue.extend({
  data: () => ({
    email: "",
    password: "",
  }),
  methods: {
    signinWithEmailandPassword(){}
  }

})
</script>
Enter fullscreen mode Exit fullscreen mode

At this point, our sign-in page should look like the below:

sign-in page

Building the chat interface

To add a chat interface for exchanging chats, we will create a pages/Chat.vue and add the code below:

<template>
  <div class="ba b--light-blue bw3 vh-100 pv5 ph2 athelas">
    <form @submit.prevent="" class="bg-lightest-blue br3 mw6 w-40-m w-70 w-20-l center pt3 shadow-5 ">
      <h2 class="near-black tc f3">Chat</h2>
      <div class="bg-white mt3 br-top pa3 vh-50 relative">
        <div class="flex justify-between items-center absolute bottom-1 right-1 left-1">
          <input type="text" class="db w-90 mr2 pv3 ph2 br3 ba" placeholder="Type message">
          <img src="../static/send.png" class="db ma w2 h2 bg-lightest-blue pa2 br2 pointer"  alt="send message image">
        </div>
      </div>
      </form> 
  </div>
</template>
<style>
  .br-top {
    border-top-left-radius: 30px;
    border-top-right-radius: 30px;
    border-bottom-left-radius: .5rem;
    border-bottom-right-radius: .5rem;
  }
</style>
Enter fullscreen mode Exit fullscreen mode

At this point, we should have our chat interface looking like the below:

chat interface

Authenticating users

Having created the signup, sign in, and chat interface, it is time we added authentication — signup and sign-in. We will start by enabling users to sign up to use the application.

Signing up users

We will start by navigating to the components/Signup.vue and updating the data property in the script tag with name, email, password, and confirmPassword to hold the user’s input. Like so:

data: () => ({
  name: "",
  email: "",
  password: "",
  confirmPassword: ""
}),
Enter fullscreen mode Exit fullscreen mode

We will proceed to import the Appwrite SDK initialized in the init.js file, like so:

import {account} from '~/init'
Enter fullscreen mode Exit fullscreen mode

We will then create a signUp function in the methods property, which gets executed when the new user tries to sign up. Like so:

signUp: async function(){
  if (this.password.length >= 8){
    if(this.password === this.confirmPassword) {
      try{
        await account.create('unique()', this.email, this.password, this.name)
        alert("account created successfully")
        window.location.href = '/chat'
      } catch (e) {
        console.log(e)
      }
    } else {
      alert("password do not match")
    }
  } else {
    alert("password length should be up to 8 characters")
  }
},
Enter fullscreen mode Exit fullscreen mode

The signUp function in the code block above does the following:

  • Verifies that the length of the password 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.

Putting everything together, our code block will look like this:

https://gist.github.com/MoeRayo/af7dfd118901de84578c15baa6e3d406

At this point, our application should look like the below:

Signup interface after putting it all together

From our Appwrite dashboard, we can view all created accounts from the Authentication > Users tab, like so:

Appwrite users dashboard

Signing users in

We need users who already have an account to sign into the application. To do this, we will navigate to the components/Signin.vue and update the data property in the script tag with email and password. This is done as follows:

data: () => ({
  email: "",
  password: "",
}),
Enter fullscreen mode Exit fullscreen mode

We will also import the Appwrite SDK initialized in the init.js file, like so:

import {account} from '~/init'
Enter fullscreen mode Exit fullscreen mode

We will then create a signIn function in the methods property, which gets executed when the new user tries to sign in. Like so:

signIn: async function () {
  try{
    await account.createEmailSession(this.email, this.password)
      window.location.href = '/chat'
    alert("user signed in")
  } catch (e){
      console.log(e)
  }
}
Enter fullscreen mode Exit fullscreen mode

The signIn function in the code block above does the following:

  • Accesses Appwrite’s services using the account.createEmailSession to create a new account session using the user’s email, password
  • window*.location.*href allows us to navigate to the chat interface after successful user login

At this point, the code within our pages/Signin.vue will look like this:

<script lang="ts">
import Vue from 'vue'
import {account} from '~/init'
export default Vue.extend({
  data: () => ({
    email: "",
    password: "",
  }),
  methods: {
    signIn: async function () {
      try{
        await account.createEmailSession(this.email, this.password)
          window.location.href = '/chat'
        alert("user signed in")
      } catch (e){
          console.log(e)
      }
    }
  }

})
</script>
Enter fullscreen mode Exit fullscreen mode

Final authentication product

At this point, we have our chat app ready for users to create a new account and sign in.

Conclusion

This article is the first part of a two-article series about building an encrypted chat application. Whereas this part discusses using Appwrite to authenticate and sign users into the application, the next part in the series will discuss sending messages and encryption.

Resources

Top comments (1)

Collapse
 
lanaschuster profile image
Lana Schuster

How to store the Cryptr secret key without exposing it in the client side?