How to build an end-to-end encrypted chat app in Next.js: Setup and Auth

Amarachi Iheanacho - Aug 26 '22 - - Dev Community

Text messages have evolved during the past decade, becoming a primary source of electronic communication. As this medium is increasingly chosen as a means of passing essential information, it becomes more necessary to control what information a user can access.

What we will be building

This article is part one of a two-part series that describes how to build an end-to-end encrypted chat with Appwrite. This post discusses authenticating and managing a user's account with the Appwrite account API.

GitHub URL

https://github.com/Iheanacho-ai/chat-app-nextjs

Prerequisites

To get the most out of this article, you’ll need the following:

  • A basic understanding of CSS, JavaScript, and React.js.
  • Docker Desktop installed on your computer; run the docker -v command to verify that we have Docker Desktop installed. If not, install it from the Get Docker documentation.
  • An Appwrite instance running on your computer. Check out this article to create a local Appwrite instance. We will use Appwrite’s powerful account service to authenticate and manage a user's account.

Setting up our Next.js app

Next.js is an open-source React framework that enables us to build server-side rendered static web applications.

To create our Next.js app, navigate to the preferred directory and run the terminal command below:

    npx create-next-app@latest
    # or
    yarn create next-app
Enter fullscreen mode Exit fullscreen mode

After creating our app, change the directory to the project and start a development server with the command below.

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

To see our app, go to http://localhost:3000.

Installing Appwrite

Appwrite is an open-source, end-to-end, backend server solution that allows developers to build applications faster.

To use Appwrite in our Next.js application, install the Appwrite client-side SDK for web applications:

    npm install appwrite
Enter fullscreen mode Exit fullscreen mode

Creating a new Appwrite project

When creating the Appwrite instance, we specified what hostname and port our console would live in. The default value is localhost:80.

Go to localhost:80 and create a new account to see the console.

On our console, we see a Create Project button. Click on it to start a new project.

Appwrite Console

The project dashboard appears once we have created the project. At the top of the page, click the Settings bar to access the Project ID and API Endpoint.

Appwrite Console

Now, copy the Project ID and API Endpoint, which we need to initialize the Appwrite Web SDK.

Appwrite Console

Next, we’ll create an init.js file in our project's root directory to initialize the Appwrite Web SDK.

    import { Client, Account } from "appwrite";
    import Router from 'next/router'; 

    export const client = new Client();
    export const account = new Account(client);

    client
        .setEndpoint('http://localhost/v1') // Your API Endpoint
        .setProject('62d9859fd5a5923edf53') // Your project ID
    ;

Enter fullscreen mode Exit fullscreen mode

Creating the signup interface

Now, we need to create a signup form for our users in our project's pages/index.jsx file.

    import Link from 'next/link';

    const Home = () => {
      return (
        <div>
          <div className="signup">
            <h2 className='form-h2'>Sign up</h2>
            <form action="">
              <label htmlFor="name">Name</label>
              <input type="text" className='signup-input' name="name" id="" />
              <label htmlFor="email">Email</label>
              <input type="email" className='signup-input' name="email" id=""/>
              <label htmlFor="password">Password</label>
              <input type="password" className='signup-input' name="password" id=""/>
              <label htmlFor="password">Confirm Password</label>
              <input type="password" className='signup-input' name="confirm password" id=""/>
              <button type='button' className= "button">Sign Up</button>
              <p>Already have an account, <Link href="/signin" className='link'>Sign in</Link></p>
            </form>
          </div>
        </div>
      )
    };
    export default Home;
Enter fullscreen mode Exit fullscreen mode

Next, we navigate to the styles folder at our project's root directory, this folder contains a global.css file. In the styles/global.css file, we’ll add the styling to our signup form.

https://gist.github.com/Iheanacho-ai/982cc4762bb129965e4a03a23a110256

Here is how our signup form looks.

Signup form

Creating the sign-in interface

If a user has already created an account with our application, then we need to create a sign-in page to allow users to sign into their account.

First, create a signin.jsx file in the pages folder. This pages/signin.jsx file will contain the piece of code in the code block below.

    const Home = () => {
      return (
        <div>
          <div className="signin">
            <h2 className='form-h2'>Sign in </h2>
            <form action="">
              <label htmlFor="email">Email</label>
              <input type="email" name= "email" className='signup-input' id=""/>
              <label htmlFor="password">Password</label>
              <input type="password" className='signup-input' name="password" id=""/>
              <button type='button' className= "button">Sign in</button>
            </form>
          </div>
        </div>
      )
    };
    export default Home;
Enter fullscreen mode Exit fullscreen mode

Next, add styling to the sign-in page.

    .signup, .signin{
      width: 400px;
      display: flex;
      flex-flow: column;
      justify-content: center;
      text-align: left;
      background: #fff;
      box-shadow: 0 10px 30px rgba(0,0,0,0.1);
      border-radius: 7px;
      margin: 10% auto;
      padding: 30px;
    }
Enter fullscreen mode Exit fullscreen mode

Go to http://localhost:3000/signin to see our sign-in page.

Appwrite Signin Page

Authenticating users

In this section of the article, we’ll discuss using the form to create, authenticate, and sign in users.

Signing up users
In the pages/index.jsx file, import the useState React hook for handling states in our application.

    import { useState } from 'react';
    import Link from 'next/link';
Enter fullscreen mode Exit fullscreen mode

Next, create state variables to store the form values in the pages/index.jsx file.

    const [name, setName] = useState()
    const [email, setEmail] = useState();
    const [password, setPassword] = useState();
    const [confirmPassword, setConfirmPassword] = useState();
Enter fullscreen mode Exit fullscreen mode

The state variables do the following:

  • The name variable stores the name of the user creating an account on our application
  • The email variable stores the email of the user
  • The password and confirmPassword variables store the password to the user’s account

Next, we’ll write a signupWithEmailandPassword function to create an account for a new user.

    const signupWithEmailandPassword = async () => {
      if (password.length >= 8) {
        if (password === confirmPassword) {
          try {
            await account.create('unique()', email, password, name)
          }catch (error) {
            console.log(error)
          }
        }else {
          alert("password do not match")
        }
      } else {
         alert('Password length should be up to 8 characters')
      }
    }
Enter fullscreen mode Exit fullscreen mode

The signupWithEmailandPassword function does the following:

  • Checks whether the user’s password is equal to or exceeds eight characters
  • Checks whether password and confirmPassword are the same
  • Creates a new user account with the user’s email, password, and name
  • Logs any error encountered to the console

Next, pass the state variables as values to the input fields and the signupWithEmailandPassword function to the onClick event listener on our Sign up button.

    <form action="">
        <label htmlFor="name">Name</label>
        <input type="text" className='signup-input' name="name" value={name} onChange={(e) => setName(e.target.value)} id="" />
       <label htmlFor="email">Email</label>
        <input type="email" className='signup-input' name="email" value={email} onChange={(e) => setEmail(e.target.value)} id=""/>
        <label htmlFor="password">Password</label>
       <input type="password" className='signup-input' name="password" value={password} onChange={(e) => setPassword(e.target.value)} id=""/>
        <label htmlFor="password">Confirm Password</label>
        <input type="password" className='signup-input' name="confirm password" value={confirmPassword} onChange={(e) => setConfirmPassword(e.target.value)} id=""/>
        <button type='button' className= "button" onClick={signupWithEmailandPassword}>Sign Up</button>
       <p>Already have an account, <Link href="/signin" className='link'>Sign in</Link></p>
    </form>
Enter fullscreen mode Exit fullscreen mode

Here is how our index.jsx file looks.

https://gist.github.com/Iheanacho-ai/72acee318020e43f85de09d17c5d73a9

Fill out the form to create an account with the application.

Appwrite Signup from

To see a list of users that have created accounts with our application, we go to the Users tab on the Appwrite Dashboard.

Appwrite Signup form

Signing in users
A user needs to sign in to create an account session. This account session allows users to interact with our application.

To sign users in, we’ll create a signinWithEmailandPassword function in the init.js file.

    import { Client, Account } from "appwrite";
    import Router from 'next/router'; 
    export const client = new Client();
    export const account = new Account(client);

    client
        .setEndpoint('http://localhost/v1') // Your API Endpoint
        .setProject('62d9859fd5a5923edf53') // Your project ID
    ;

    export const signinWithEmailandPassword = async (email, password) => {
        try {
            await account.createEmailSession(email, password)
            Router.push("/chat")
        } catch (error) {
            console.log(error)
        }
    }
Enter fullscreen mode Exit fullscreen mode

The signinWithEmailandPassword function above does the following:

  • Creates a user session with the Appwrite createEmailSession function. This function receives email and password parameters.
  • Navigates to the chat route after a user signs in.
  • Logs any error encountered in the console.

Immediately after creating an account, we want a user to sign into our application. To do this, import the signinWithEmailandPassword function in the pages/index.jsx file.

    import { account, signinWithEmailandPassword } from '../init';
Enter fullscreen mode Exit fullscreen mode

Next, we’ll call the signinWithEmailandPassword function in the signupWithEmailandPassword function.

    const signupWithEmailandPassword = async () => {
       if (password.length >= 8) {
        if (password === confirmPassword) {
          try {
            await account.create('unique()', email, password, name)
             alert("account created successfully")
            // signes in the user
            signinWithEmailandPassword(email, password)    
           } catch (error) {
            console.log(error)
          }
         } else {
         alert("password do not match")
        }
      } else {
         alert('Password length should be up to 8 characters')
      }
    }
Enter fullscreen mode Exit fullscreen mode

We then import the signinWithEmailandPassword function in our pages/signin.jsx file.

    import { useState } from 'react';
    import { signinWithEmailandPassword } from '../init';
Enter fullscreen mode Exit fullscreen mode

Next, we’ll create state variables to store our input field values email and password.

    const [email, setEmail] = useState();
    const [password, setPassword] = useState();
Enter fullscreen mode Exit fullscreen mode

After creating the state variables, create a handleSignin function that calls the signinWithEmailandPassword function.

    const handleSignin = () => {
      signinWithEmailandPassword(email, password)
    }
Enter fullscreen mode Exit fullscreen mode

We then pass the state variables as values to the input fields and the handleSignin function to the onClick event listener on our Sign in button.

    <div>
      <div className="signin">
        <form action="">
          <label htmlFor="email">Email</label>
          <input type="email" className='signup-input' name="email" value={email} onChange={(e) => setEmail(e.target.value)} id=""/>
          <label htmlFor="password">Password</label>
          <input type="password" className='signup-input' name="" value={password} onChange={(e) => setPassword(e.target.value)} id=""/>
            <button type='button' className= "button" onClick={handleSignin}>Sign in</button>
        </form>
      </div>
    </div>
Enter fullscreen mode Exit fullscreen mode

Here is how our pages/signin.jsx file should look at this point.

    import { useState } from 'react';
    import { signinWithEmailandPassword } from '../init';

    const Home = () => {
      const [email, setEmail] = useState();
      const [password, setPassword] = useState();
      const handleSignin = () => {
        signinWithEmailandPassword(email, password)
      }
      return (
        <div>
          <div className="signin">
            <form action="">
              <label htmlFor="email">Email</label>
              <input type="email" className='signup-input' name="email" value={email} onChange={(e) => setEmail(e.target.value)} id=""/>
              <label htmlFor="password">Password</label>
              <input type="password" className='signup-input' name="" value={password} onChange={(e) => setPassword(e.target.value)} id=""/>
              <button type='button' className= "button" onClick={handleSignin}>Sign in</button>
            </form>
          </div>
        </div>
      )
    };

    export default Home;
Enter fullscreen mode Exit fullscreen mode

Appwrite Signin Form

Creating the chat interface

Let’s create a chat.jsx file in the pages folder. This file will contain the piece of code below.

    const Chat = () => {
      return (
        <div className='chat'>
          <div className='user-chat'>
            <div className="user-chat-header">USER</div>
            <div className='messages'>
            </div>
            <div className='input-area'>
              <input type="text" className='message-input'/>
              <button className='send' type='button'>send</button>
            </div>
          </div>
        </div>
      ) 
    };

   export default Chat;
Enter fullscreen mode Exit fullscreen mode

In the global.css file, we'll add styling to our chat interface.

https://gist.github.com/Iheanacho-ai/93956e8a24effe332efe44e4d15a8347

Here is how our chat app user interface looks.

Chat App interface

Chat App

Conclusion

This article discussed using Appwrite’s Account service to authenticate and sign a user into an application.

Resources

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .