DEV Community

Emore Ogheneyoma Lawrence
Emore Ogheneyoma Lawrence

Posted on • Updated on

Authentication in React with Appwrite

Table of Contents

Introduction

User Authentication as we know it today has become very important in Web applications. Authentication is simply the process of identifying a users identity. For so long now, things like user Authentication has been handled by custom backend in applications which then sends the data to the frontend and all the magic happens, it's usually time consuming, complex and hard to grasp especially if you're not a backend developer. During the course of this article, we will implement user authentication in React web applications using Appwrite.

Appwrite is a versatile open-source backend server that offers various functionalities like authentication, database, storage, and server-side functions. It enables developers to build full-stack applications without requiring a separate backend. With Appwrite, React developers can create real-time applications effortlessly, leveraging its comprehensive set of APIs and services.

Appwrite can be categorized as a Backend as a Service (BaaS) platform. It offers a range of backend functionalities such as authentication, database, storage, and server-side functions, which developers can utilize to build applications without needing to manage the backend infrastructure themselves.

This article will guide you through implementing User Authentication using the AppWrite Auth functionality.

For this article, it's helpful to have a basic understanding of handling forms in React and familiarity with React Router. Don't worry if you're not an expert yet, you can still follow along and learn as you go!

React Project Setup with Appwrite

To initiate our project setup, the first step involves registering and creating an account with AppWrite

After registering, create a new project in appwrite by clicking on the Create a new project button. You would then see a User Interface (UI) like this:

Create Appwrite Project

To get started, you'll need to give your project a name. We'll use react-appwrite-auth for this example. Don't worry about the Project ID, it will be automatically generated for you when you click Next

On click of the Next button you'll be routed to a page where you'll be asked to choose your region. Choose the nearest available region for optimal performance.
Next click on the Create button, this will redirect you to your project dashboard where you'll be able to Add a platform. Since we're using React, we will go with the Web Platform

Web Platform

On Click of the Web Platform we are presented with a screen in the screenshot below.

Register Hostname

These two fields are required. Set the name as React Appwrite Auth and the hostname as localhost. Alternatively, you can use * as your hostname.

We would then see two optional steps that guide us on how to Install the appwrite SDK into our app and Initialize and configure the API Key. We would come back to use them after we've successfully created our React application locally.

After these we would see the screen in the screenshot below.

Project setup complete

Hooray! We have successfully setup our project on the Appwrite Platform.

Now, Let us create a React Project using vite. Run the following commands to create a React project.

Create a project directory named react-appwrite-auth and open it in your favorite code editor. Then run the following command that helps to scaffold(find a more simple word) a project for you.

# npm 
npm create vite@latest

# Yarn 
yarn create vite
Enter fullscreen mode Exit fullscreen mode

You will then be prompted and asked to configure how you want your project to be configured. The screenshot below should direct you on what you should pick

Vite-Project-Scaffolding

Entering ./ as the project name in Vite scaffolds the React project in your current terminal directory. Choose React from the framework options, and select the Javascript variant.
After creating the project, npm install fetches the necessary tools and npm run dev starts a live development server for quick updates.

Now let us install Appwrite into our created React Project and configure the API keys

To install Appwrite, run the following command in the projects directory

  npm install appwrite
Enter fullscreen mode Exit fullscreen mode

Next, create a .env.local file in the project root directory and paste the following

  VITE_APPWRITE_PROJECT_ID="PROJECT_ID"
Enter fullscreen mode Exit fullscreen mode

To obtain your PROJECT_ID, navigate to your project created on appwrite, in our case, navigate to the react-appwrite-auth project, you will be able to see it here.
You can also navigate to the react-appwrite-auth project dashboard > settings, you will be able to see your PROJECT_ID similar to the screenshot below

project-id

Then create a appwriteConfig.js file in the src folder of your React app and paste the following code

  import { Account, Client } from "appwrite"; //  Import Client from "appwrite"

  const client = new Client(); // Create a new Client instance
  client
      .setEndpoint("https://cloud.appwrite.io/v1")
      .setProject(import.meta.env.VITE_APPWRITE_PROJECT_ID)

  export const account = new Account(client); // Export an Account instance initialized with the client
Enter fullscreen mode Exit fullscreen mode

We have successfully created the Appwrite client by passing in the Project EndPoint and Project ID and then exporting the account instance that will be used later on in the project.

Building the User Interface(UI)

This section focuses on crafting UI components for our React app, prioritizing simplicity and functionality due to our authentication focus. We'll build four key components:

  1. Login Page: Initial access point for users to input credentials and log in
  2. Register Page: Allows new users to create accounts by providing essential details.
  3. Home Page (Private Route): Authenticated users' exclusive space, displaying user email and ID from Appwrite.
  4. The Navbar Component

Login Page

import { useState } from "react";
import { Link } from "react-router-dom";
import "./Login.css";

const Login = () => {
  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");

  const handleSubmit = async (e) => {
    e.preventDefault();
    try {
      if (password === "" || email === "") {
        alert("Please fill in the field required")
      } else {
        alert("Data Validated 🚀");
      }

      // Call Appwrite function to handle login with email and password
    } catch (err) {
      alert(err.message)
    }
  };

  return (
    <div className="loginPage">
      <h2>Login</h2>
      <form onSubmit={handleSubmit}>
        <div>
          <label htmlFor="email">Email:</label>
          <input
            type="email"
            required
            id="email"
            value={email}
            onChange={(e) => setEmail(e.target.value)}
          />
        </div>

        <div>
          <label htmlFor="password">Password:</label>
          <input
            type="password"
            required
            id="password"
            value={password}
            onChange={(e) => setPassword(e.target.value)}
          />
        </div>
        <button type="submit">Login</button>

        <div>
          Dont have an account? <Link to="/register">Register</Link>
        </div>
      </form>
    </div>
  );
};

export default Login;
Enter fullscreen mode Exit fullscreen mode
.loginPage > form > *:not(:last-child) {
  margin-bottom: 1rem;
}
Enter fullscreen mode Exit fullscreen mode

Register Page


import { useState } from "react";
import { Link } from "react-router-dom";
import "./Register.css";

const Register = () => {
  const [username, setUsername] = useState("");
  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");
  const [confirmPassword, setConfirmPassword] = useState("");

  const handleSubmit = async (e) => {
    e.preventDefault();
    try {
      // Call Appwrite function to handle user registration
      if (password !== confirmPassword) {
        alert("Passwords do not match");
        return;
      }
      if (
        username === "" ||
        email === "" ||
        password === "" ||
        confirmPassword === ""
      ) {
        alert("Please fill in all fields");
        return;
      }

      alert("Data is validated 🚀");
    } catch (err) {
      alert(err.message);
    }
  };
  return (
    <div className="registerPage">
      <h2>Register</h2>
      <form onSubmit={handleSubmit}>
        <div>
          <label htmlFor="username">Username:</label>
          <input
            type="text"
            required
            id="username"
            value={username}
            onChange={(e) => setUsername(e.target.value)}
          />
        </div>

        <div>
          <label htmlFor="email">Email:</label>
          <input
            type="email"
            required
            id="email"
            value={email}
            onChange={(e) => setEmail(e.target.value)}
          />
        </div>

        <div>
          <label htmlFor="password">Password:</label>
          <input
            type="password"
            required
            id="password"
            value={password}
            onChange={(e) => setPassword(e.target.value)}
          />
        </div>

        <div>
          <label htmlFor="confirmPassword">Confirm Password:</label>
          <input
            type="password"
            required
            id="confirmPassword"
            value={confirmPassword}
            onChange={(e) => setConfirmPassword(e.target.value)}
          />
        </div>
        <button type="submit">Register</button>

        <div>
          Have an account? <Link to="/login">Login</Link>
        </div>
      </form>
    </div>
  );
};

export default Register;

Enter fullscreen mode Exit fullscreen mode
.registerPage > form > *:not(last-child) {
  margin-bottom: 1rem;
}
Enter fullscreen mode Exit fullscreen mode

Home Page

import "./Home.css";

const Home = () => {
  return (
    <div>
      <h1>Home Page</h1>
      <p>This page is a Protected Page and should only be seen by Authenticated Users</p>
    </div>
  );
};

export default Home;

Enter fullscreen mode Exit fullscreen mode

Navbar Component

import { Link } from "react-router-dom";
import "./Navbar.css";
import { useNavigate } from "react-router-dom";

const Navbar = () => {
    const navigate = useNavigate();
    const handleLogin = () => {
        navigate("/login")
    }
  return (
    <nav>
      <div className="navLogo">Logo</div>


      <Link to="/" className="navHomeLink">Home</Link>
      <button onClick={handleLogin} className="navLoginButton">Login</button>
    </nav>
  );
};

export default Navbar;

Enter fullscreen mode Exit fullscreen mode
nav {
  /* max-width: 768px; */
  margin-inline: auto;
  display: flex;
  align-items: center;
  justify-content: space-between;
  border-bottom: 3px solid black;
}

nav > .navLogo {
  font-size: 2.5rem;
}

nav > .navHomeLink {
  font-size: 1.4rem;
}

nav > button {
  background-color: transparent;
  border: 1px solid gray;
  font-size: 1.2rem;
  cursor: pointer;
}
Enter fullscreen mode Exit fullscreen mode

If you've followed the UI code snippets, you'll have a basic app that looks like this:

Gif UI

Set up React Context for User State Management

In your root directory, create a context folder. Inside it, add a file named UserAuthContext.jsx. Paste the provided code into this file to handle user authentication efficiently.

import { createContext, useEffect, useState } from "react";
import { account } from "../appwriteConfig";

export const UserAuthContext = createContext();

export const UserProvider = ({ children }) => {
  const [user, setUser] = useState(null);

  const [isLoading, setIsLoading] = useState(true); // Track loading state

  useEffect(() => {
    const fetchUserData = async () => {
      try {
        const response = await account.get(); // Fetch user data
        setUser(response); // Set user data 
      } catch (error) {
        console.error("Error fetching user data:", error);
      } finally {
        setIsLoading(false); // Always set loading state to false after fetching
      }
    };

    fetchUserData();
  }, []);

  return (
    <UserAuthContext.Provider value={{ user, setUser, isLoading }}>
      {children}
    </UserAuthContext.Provider>
  );
};
Enter fullscreen mode Exit fullscreen mode

Top comments (0)