User Authentication in Next.js with Strapi

Shada - Feb 22 '22 - - Dev Community

In this tutorial, we'll be building a Next.js application and authenticate that with Strapi and NextAuth.

Introduction

Strapi is the leading open-source headless CMS. It's 100% Javascript, fully customizable, and developer-first. I've been using Strapi for some of my Open Source projects, and the developer experience is excellent. It has helped me build prototypes and products much faster.

Next.js is a React framework for building Server-side rendered applications. It has many features with a good developer experience and supports TypeScript out of the box.

NextAuth is an authentication library built for use with Next.js. It's easy, fast, and secure.

The code for this tutorial is available on GitHub.

Software Required to Run the Application

  1. Docker (Click here to download.)
  2. npm (Click here to download.)
  3. yarn (Click here to download.)

Creating a New Next.js Application

Let's start by creating a new Next.js application. We can create a Next.js application using the following command:

    yarn create next-app
Enter fullscreen mode Exit fullscreen mode

The above command will ask for the name of the project. We'll call it “frontend”.

Once the setup of the project and installing all dependencies are complete, we can go inside the frontend directory and start the application using the following command:

    cd frontend && yarn dev
Enter fullscreen mode Exit fullscreen mode

The above command will start the application on http://localhost:3000/.

Next.js Default Home Page

Creating a New Strapi Application

In this section, we'll create a new Strapi application using Docker. More information about how to create a new Strapi application using Docker can be obtained from their repository. We'll use Postgres to do this.

We can create a new directory and name it “backend” in the root of our project. We can create a new file docker-compose.yml inside the backend directory with the following content:

# backend/docker-compose.yml

version: '3'
services:
  strapi:
    container_name: strapi
    image: strapi/strapi
    environment:
      - DATABASE_CLIENT=postgres
      - DATABASE_HOST=db
      - DATABASE_PORT=5432
      - DATABASE_NAME=strapi
      - DATABASE_USERNAME=strapi
      - DATABASE_PASSWORD=strapi
    ports:
      - 1337:1337
    volumes:
      - ./app:/srv/app
    depends_on:
      - db
  db:
    container_name: postgres
    image: postgres
    restart: always
    volumes:
      - ./db:/var/lib/postgresql/data
    ports:
      - 5432:5432
    environment:
      POSTGRES_USER: strapi
      POSTGRES_PASSWORD: strapi
      POSTGRES_DB: strapi
Enter fullscreen mode Exit fullscreen mode

Now, we need to start Docker and run the following command inside the backend directory to create our new Strapi application:

    docker-compose up
Enter fullscreen mode Exit fullscreen mode
  • When running this image, Strapi will check if there is a project in the /srv/app folder of the container. If there is nothing, it will run the Strapi new command in the container /srv/app folder.

Strapi Admin Console

Once the setup of our Strapi application is complete, we'll be able to view the administration panel at http://localhost:1337/admin.

Next, we'll need to create our first administrator in order to log into the Strapi administration panel:

Strapi Admin Registration

Once we create our first administrator, we'll be logged into the Strapi administration panel:

Strapi Admin Dashboard Home Page

Creating and Integrating Google OAuth Client into our Applications

In this section, we'll create a new Google OAuth Client and integrate it into Next.js and Strapi applications.

First, we'll need to visit the Google Developer Console to create a new OAuth Client and copy the credentials (Client ID and Client Secret) in our frontend/**.env** file:

  1. Create a new OAuth client ID.
  2. Choose “web application” as the application type.
  3. Add the following authorized redirect URLs:
  • http://localhost:3000/api/auth/callback/google
  • http://localhost:1337/connect/google/callback

Callback URL Settings

Once we click on the save button, we'll get our new OAuth Client's credentials. We can create a new file .env inside our frontend directory to store all the credentials:

// frontend/.env

NEXT_PUBLIC_API_URL=http://localhost:1337
NEXT_PUBLIC_DATABASE_URL=postgres://strapi:strapi@localhost:5432/strapi?synchronize=true
NEXTAUTH_URL=http://localhost:3000
GOOGLE_CLIENT_ID="12345.apps.googleusercontent.com"
GOOGLE_CLIENT_SECRET="1234-567-9"
Enter fullscreen mode Exit fullscreen mode

You'll need to replace the values of GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET with the values of your new Google OAuth Client.

Next, let's add these credentials to our Strapi application as well. In the Strapi administration panel, we need to add the Google OAuth Client credentials and enable the Google provider. The values can be added in the Providers menu inside the Settings tab.

We need to enter the Client ID and Client Secret in the Google provider and enable that:

Google Login Provider

Installing and Integrating NextAuth with Next.js and Strapi

In this section, we'll be installing and integrating NextAuth.

We can run the following command inside our frontend directory to install NextAuth as a dependency:

    yarn add next-auth
Enter fullscreen mode Exit fullscreen mode

Next, we'll need to create a new file, ...nextauth.js, inside frontend/pages/api/auth directory with the following content:

// frontend/pages/api/auth/[...nextauth].js
Enter fullscreen mode Exit fullscreen mode
    import NextAuth from "next-auth";
    import Providers from "next-auth/providers";

    const options = {
      providers: [
        Providers.Google({
          clientId: process.env.GOOGLE_CLIENT_ID,
          clientSecret: process.env.GOOGLE_CLIENT_SECRET,
        }),
      ],
      database: process.env.NEXT_PUBLIC_DATABASE_URL,
      session: {
        jwt: true,
      },
      callbacks: {
        session: async (session, user) => {
          session.jwt = user.jwt;
          session.id = user.id;
          return Promise.resolve(session);
        },
        jwt: async (token, user, account) => {
          const isSignIn = user ? true : false;
          if (isSignIn) {
            const response = await fetch(
              `${process.env.NEXT_PUBLIC_API_URL}/auth/${account.provider}/callback?access_token=${account?.accessToken}`
            );
            const data = await response.json();
            token.jwt = data.jwt;
            token.id = data.user.id;
          }
          return Promise.resolve(token);
        },
      },
    };

    const Auth = (req, res) =>
      NextAuth(req, res, options);

    export default Auth;
Enter fullscreen mode Exit fullscreen mode

In the NextAuth callback function, we're calling the Strapi Authentication API endpoint. We're storing the JWT (token.jwt) and user ID (data.user.id) from the data that the Strapi API sends us.

In this way, we can understand which user is currently authenticated.

We can get the details of the authenticated users from the [getSession](https://next-auth.js.org/getting-started/client#getsession) function of NextAuth. If the getSession function doesn't return us any details, we can assume that the user is not authenticated.

We'll also need to add the pg package, so that NextAuth can connect to our database directly. We can install that package using the following command from our frontend directory:
yarn add pg

We can verify whether NextAuth is working with our application by updating our frontend/pages/index.js with the following content:

// frontend/pages/index.js
Enter fullscreen mode Exit fullscreen mode
    import { getSession, signIn, signOut } from "next-auth/client";
    import Head from 'next/head';
    import Link from "next/link";
    import React from "react";

    const IndexPage = ({
      session,
    }) => {
      const signInButtonNode = () => {
        if (session) {
          return false;
        }

        return (
          <div>
            <Link href="/api/auth/signin">
              <button
                onClick={(e) => {
                  e.preventDefault();
                  signIn();
                }}
              >
                Sign In
              </button>
            </Link>
          </div>
        );
      };

      const signOutButtonNode = () => {
        if (!session) {
          return false;
        }

        return (
          <div>
            <Link href="/api/auth/signout">
              <button
                onClick={(e) => {
                  e.preventDefault();
                  signOut();
                }}
              >
                Sign Out
              </button>
            </Link>
          </div>
        );
      };

      if (!session) {
        return (
          <div className="hero">
            <div className="navbar">
              {signOutButtonNode()}
              {signInButtonNode()}
            </div>
            <div className="text">
              You aren't authorized to view this page
            </div>
          </div>
        )
      }

      return (
        <div className="hero">
          <Head>
            <title>Index Page</title>
          </Head>
          <div className="navbar">
            {signOutButtonNode()}
            {signInButtonNode()}
          </div>
          <div className="text">
            Hello world
          </div>
        </div>
      );
    };

    export const getServerSideProps = async ({ req }) => {
      const session = await getSession({ req });
      return {
        props: {
          session,
        },
      };
    };

    export default IndexPage;
Enter fullscreen mode Exit fullscreen mode

Now, if we visit http://localhost:3000/, we should be able to view the following screen:

Next.js Sign In Page

We can log in using our Gmail account once we click on the Sign In button. Once we're signed in, we should be able to view the following screen on http://localhost:3000/:

Next.js Protected Page

All the details of the authenticated user are present in the session prop of the page:

Next.js Sign out Page

We can show the details of the authenticated user by fetching the details from the session prop. Also, the authenticated user will now be visible in the Strapi administration panel:

Strapi User Database

Conclusion

In this tutorial, we learned how we can authenticate a Next.js application using Strapi and NextAuth. We've worked with the REST API that Strapi provides out of the box. However, a similar solution can also be implemented for using GraphQL.

The code for this tutorial is available on GitHub. I've also created a boilerplate for using Strapi with Next.js. It'll help you get up and running with Strapi and Next.js really quickly. Feel free to give it a try.

Get started with Strapi by creating a project using a starter or trying our live demo. Also, consult our forum if you have any questions. We will be there to help you.

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