Manage User Authentication in Next.js 13

Demola Malomo - Jan 4 '23 - - Dev Community

Authentication is one of the most critical aspects of building web applications. It helps protect your users’ data and provides an extra layer of security by preventing unauthorized access. Next.js 13 handles authentication in the simplest yet most effective way with advanced routing for progressive web applications.

This article will highlight the new Next.js features on authentication and how you can use Appwrite to implement user authentication. The project repository can be found here.

Prerequisites

  • Node.js installed - It comes with Node Package Module(NPM) that is used to install and manage packages for Next.js and Appwrite web SDK
  • Docker installed - Docker is used to install Appwrite locally
  • Basic knowledge of React

Next.js version 13 brings enhanced features and performance, including new features for implementing authentication and creating protected routes by introducing middleware to enable full flexibility with the Next.js router.

This article will demonstrate how to use Appwrite to authenticate users in a Next.js application.

Authentication using Appwrite

Appwrite is an open-source project that provides an end-to-end solution for managing the entire lifecycle of your app, from development to deployment. It is designed to make it easy to develop, test, and deploy your app with minimal configuration.

Installing Appwrite

First, ensure that Docker is properly installed in your development environment. You can confirm the installation by running the following command on your terminal:

docker
Enter fullscreen mode Exit fullscreen mode

To install Appwrite, use the following commands based on your operating system:

Windows Command Prompt(CMD):

docker run -it --rm ^
    --volume //var/run/docker.sock:/var/run/docker.sock ^
    --volume "%cd%"/appwrite:/usr/src/code/appwrite:rw ^
    --entrypoint="install" ^
    appwrite/appwrite:1.1.1
Enter fullscreen mode Exit fullscreen mode

Unix:

docker run -it --rm \
    --volume /var/run/docker.sock:/var/run/docker.sock \
    --volume "$(pwd)"/appwrite:/usr/src/code/appwrite:rw \
    --entrypoint="install" \
    appwrite/appwrite:1.1.1
Enter fullscreen mode Exit fullscreen mode

Once the installation is complete, open a web browser and load the Appwrite server URL from the installation configurations:


localhost:<port_number>
Enter fullscreen mode Exit fullscreen mode

On loading the Appwrite on your browser, you will be redirected to a login page:

Create a new user account by clicking on the Sign Up link. After successful registration, you will be redirected to a new page to create a new project:

Enter the name of the project and proceed to create a project:

Set up a Next.js project

To set up a project using the latest version of Next.js, 13, run the following command on your command line tool:

npx create-next-app@latest
Enter fullscreen mode Exit fullscreen mode

or

yarn create next-app
Enter fullscreen mode Exit fullscreen mode

The new version asks for a few questions; you can answer them as shown below:

Would you like to use TypeScript? <No>
Would you like to use ESLint? <No>
Would you like to use Tailwind CSS? <Yes>
Would you like to use `src/` directory? <Yes>
Would you like to use App Router? (recommended) <Yes>
Would you like to customize the default import alias? <Yes>
What import alias would you like configured? @/* <Press Enter>
Enter fullscreen mode Exit fullscreen mode

Connecting to Appwrite

To connect a Next.js application with Appwrite, install the appwrite module using:

npm install appwrite
Enter fullscreen mode Exit fullscreen mode

Create a new directory called utils, and inside add a new appwrite configuration file: appwriteClient.js

Create a library file to establish the connection and use the code snippet below:

import { Client, Account, ID } from 'appwrite';

//create client
const client = new Client();
client
    .setEndpoint('http://localhost/v1')
    .setProject('YOUR PROJECTID GIES HERE');

//create account
const account = new Account(client);

export const createAccount = (email, password, name) =>
    account.create(ID.unique(), email, password, name);

export const createUserSession = (email, password) =>
    account.createEmailSession(email, password);

export const getAccount = () => account.get();

export const logout = () => account.deleteSession('current');
Enter fullscreen mode Exit fullscreen mode

Code Explanation:

export const createAccount = async (email, password, name) =>
    account.create(ID.unique(), email, password, name);
Enter fullscreen mode Exit fullscreen mode

The createAccount function takes an email, password, and name parameters. The function calls the create method on the account instance to register a new user by passing a unique ID generated using ID.unique() and the provided email, password, and name values.

export const createUserSession = async (email, password) =>
    account.createEmailSession(email, password);
Enter fullscreen mode Exit fullscreen mode

The createUserSession function takes an email and password parameter. The function calls the createEmailSession method to log in users using the account instance and passing the provided email and password.

export const getAccount = () => account.get();

export const logout = () => account.deleteSession('current');
Enter fullscreen mode Exit fullscreen mode

The getAccount and logout function gets details of logged-in users and log-out users, respectively.

Building the application UI

The application will consist of three pages; a sign-in, sign-up, and an authenticated page.

Sign-in page

To do this, first, you will create a signin/page.js file inside the app folder and add the snippet below:

'use client';
import { useRouter } from 'next/navigation';
import { useState } from 'react';
import { createUserSession } from '../../../utils/appwriteClient';
import Link from 'next/link';

export default function SignIn() {
    const router = useRouter();
    const [value, setValue] = useState({ email: '', password: '' });
    const [isLoading, setIsLoading] = useState(false);

    const handleChange = (e) => {
        setValue({ ...value, [e.target.name]: e.target.value });
    };

    const handleSubmit = (e) => {
        e.preventDefault();
        setIsLoading(true);
        const { email, password } = value;
        createUserSession(email, password)
            .then((res) => {
                setIsLoading(false);
                router.push('/');
            })
            .catch((e) => {
                setIsLoading(false);
                alert('Incorrect email or password!!!');
            });
    };
    return (
        //UI code goes here
    );
}
Enter fullscreen mode Exit fullscreen mode

The snippet above imports the required dependencies, manage the application state, and creates an handleSubmit function that uses the createUserSession to authenticate users.

Lastly, you will update the application UI using the states and function created above.

//import goes here

export default function SignIn() {
    //states and function goes here

    return (
        <div className='w-screen h-screen bg-white'>
            <main className='py-4 px-4 lg:py-10 lg:px-10 w-full'>
                <div className='flex justify-center mb-8'>
                    <h1 className='text-2xl font-medium text-gray-700'>
                        Authentication with Appwrite and Nextjs 13
                    </h1>
                </div>
                <section className='flex justify-center'>
                    <div className='px-4 py-2 border rounded-lg w-full lg:w-2/4'>
                        <div className='border-b h-8 mb-4'>
                            <h3 className='text-gray-700'>
                                Sign in with your details
                            </h3>
                        </div>
                        <form onSubmit={handleSubmit}>
                            <fieldset>
                                <label className='text-sm text-gray-400 mb-4 block'>
                                    Email
                                </label>
                                <input
                                    name='email'
                                    className='border w-full rounded-sm mb-6 p-2'
                                    required
                                    value={value.email}
                                    onChange={handleChange}
                                    type='email'
                                />
                            </fieldset>
                            <fieldset>
                                <label className='text-sm text-gray-400 mb-4 block'>
                                    Password
                                </label>
                                <input
                                    name='password'
                                    className='border w-full rounded-sm mb-6 p-2'
                                    required
                                    value={value.password}
                                    onChange={handleChange}
                                    type='password'
                                />
                            </fieldset>
                            <button
                                className='text-sm text-white px-8 py-2 rounded-sm bg-blue-600 hover:bg-blue-700'
                                disabled={isLoading}
                            >
                                Sign in
                            </button>
                            <div className='flex mt-6'>
                                <p>Don't have an account?</p>{' '}
                                <Link
                                    href='/signup'
                                    className='ml-2 text-blue-600 font-medium'
                                >
                                    Sign up
                                </Link>
                            </div>
                        </form>
                    </div>
                </section>
            </main>
        </div>
    );
}
Enter fullscreen mode Exit fullscreen mode

Sign-up page

In the same app folder, you will first create a signup/page.js file and add the snippet below:

'use client';
import { useRouter } from 'next/navigation';
import { useState } from 'react';
import { createAccount } from '../../../utils/appwriteClient';
import Link from 'next/link';

export default function SignUp() {
    const router = useRouter();
    const [value, setValue] = useState({ name: '', email: '', password: '' });
    const [isLoading, setIsLoading] = useState(false);

    const handleChange = (e) => {
        setValue({ ...value, [e.target.name]: e.target.value });
    };

    const handleSubmit = (e) => {
        e.preventDefault();
        setIsLoading(true);
        const { email, name, password } = value;
        createAccount(email, password, name)
            .then((res) => {
                setIsLoading(false);
                alert('Account created successfully! you can now login');
                router.push('/signin');
            })
            .catch((e) => {
                setIsLoading(false);
                alert('Error creating user');
            });
    };
    return (
        //UI code goes here
    );
}
Enter fullscreen mode Exit fullscreen mode

The snippet above imports the required dependencies, manage the application state, and creates an handleSubmit function that uses the createAccount to sign-up new users.

Lastly, you will update the application UI using the states and function created above.

//import goes here

export default function SignUp() {
    //states and function goes here

    return (
        <div className='w-screen h-screen bg-white'>
            <main className='py-4 px-4 lg:py-10 lg:px-10 w-full'>
                <div className='flex justify-center mb-8'>
                    <h1 className='text-2xl font-medium text-gray-700'>
                        Authentication with Appwrite and Nextjs 13
                    </h1>
                </div>
                <section className='flex justify-center'>
                    <div className='px-4 py-2 border rounded-lg w-full lg:w-2/4'>
                        <div className='border-b h-8 mb-4'>
                            <h3 className='text-gray-700'>
                                Sign up with your details
                            </h3>
                        </div>
                        <form onSubmit={handleSubmit}>
                            <fieldset>
                                <label className='text-sm text-gray-400 mb-4 block'>
                                    Name
                                </label>
                                <input
                                    name='name'
                                    className='border w-full rounded-sm mb-6 p-2'
                                    required
                                    value={value.name}
                                    onChange={handleChange}
                                    type='text'
                                />
                            </fieldset>
                            <fieldset>
                                <label className='text-sm text-gray-400 mb-4 block'>
                                    Email
                                </label>
                                <input
                                    name='email'
                                    className='border w-full rounded-sm mb-6 p-2'
                                    required
                                    value={value.email}
                                    onChange={handleChange}
                                    type='email'
                                />
                            </fieldset>
                            <fieldset>
                                <label className='text-sm text-gray-400 mb-4 block'>
                                    Password
                                </label>
                                <input
                                    name='password'
                                    className='border w-full rounded-sm mb-6 p-2'
                                    required
                                    value={value.password}
                                    onChange={handleChange}
                                    type='password'
                                />
                            </fieldset>
                            <button
                                className='text-sm text-white px-8 py-2 rounded-sm bg-blue-600 hover:bg-blue-700'
                                disabled={isLoading}
                            >
                                Sign up
                            </button>
                            <div className='flex mt-6'>
                                <p>Already have an account?</p>{' '}
                                <Link
                                    href='/signin'
                                    className='ml-2 text-blue-600 font-medium'
                                >
                                    Sign in
                                </Link>
                            </div>
                        </form>
                    </div>
                </section>
            </main>
        </div>
    );
}
Enter fullscreen mode Exit fullscreen mode

Authenticated page

Lastly, you will modify the page.js file inside the app folder as shown below:

'use client';
import { useRouter } from 'next/navigation';
import { useEffect, useState } from 'react';
import { getAccount, logout } from '../../utils/appwriteClient';

export default function Home() {
    const [session, setSession] = useState(null);
    const router = useRouter();
    const getSession = () => {
        getAccount()
            .then((res) => setSession(res))
            .catch((err) => {
                router.push('/signin');
                console.log(err);
            });
    };

    useEffect(() => {
        getSession();
    }, []);
    const signOut = () => {
        logout();
        alert('logout successful');
        router.push('/signin');
    };
    return (
        <div className='w-screen h-screen bg-white'>
            <main className='py-4 px-4 lg:py-10 lg:px-10 w-full'>
                <div className='flex justify-center mb-8'>
                    <h1 className='text-2xl font-medium text-gray-700'>
                        Welcome
                    </h1>
                </div>
                <section className='flex justify-center'>
                    <div className='px-4 py-2 border rounded-lg w-full lg:w-2/4'>
                        <h3 className='text-gray-700'>
                            Name: {session && session.name}
                        </h3>
                        <h3 className='text-gray-700 mt-4'>
                            Name: {session && session.email}
                        </h3>
                        <button
                            className='text-sm text-white px-8 py-2 rounded-sm bg-black hover:bg-gray-800 mt-6'
                            onClick={signOut}
                        >
                            Log out
                        </button>
                    </div>
                </section>
            </main>
        </div>
    );
}
Enter fullscreen mode Exit fullscreen mode

The snippet above does the following:

  • Imports the required dependencies.
  • Creates states and route variables to manage logged-in user sessions and routes accordingly.
  • Creates a getSession function to get the logged-in user session and re-route to the login page if the session is empty.
  • Creates a signOut function to log the user out
  • Updates the UI to show logged-in user’s name and email

With that done, you can test the application by running the command below:

npm run dev
Enter fullscreen mode Exit fullscreen mode

or

yarn dev
Enter fullscreen mode Exit fullscreen mode

Demo 1

Demo 2

You can also validate the authenticated user details on the Appwrite console:

Auth user

Conclusion

In conclusion, this post highlights how Appwrite provides an easy and secure way to manage authentication in Next.js applications. With Appwrite, developers can quickly and easily establish authentication, manage users, and provide role-based access control to their applications.

Resources

The following resources might be helpful:

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