NextAuth Guide

I. Opening

Authentication is an important feature for users to show their basic information, access protected routes, and use some other features that required users’ information. Therefore, implementing authentication for Web Apps must be one of the most basic skills and prioritized to be done.

This article will show how to implement the library Next-Auth.js for NextJS projects & build a basic authentication flow including some features like Sign out, Sign In with Google, Facebook, Github & custom credentials, custom login API…

II. About NextAuth.js

NextAuth.js will help us to handle almost everything about authentication, it’s designed to be used synchronously with OAuth services.

It allows us to manage sessions with Database: MySQL, MongoDB, PostgreSQL,… or with JSON WEB TOKEN.

Next-Auth.js provides a client-side API to interact with session data in our application. Session data is returned from Providers, including name, avatar, email,… For applications using React, there’s also a hook useSession() to get the current authentication status & extract session data, which is very convenient to use.

III. Installing

I created a project to have a better understanding of what we’re achieving. To get started, fork this repo () and clone it with

git clone https://github.com//Next-Auth-Guide.git

This project has 2 branches: main & starter. In the starter, i prepared some basic components such as Private Layout, Auth Layout, and some routes: /index, /register, /login, /profile,… For each route, we will have to add authentication for it. Let’s get started with the starter, in your terminal:

The app will run on http://localhost:3000/

yarn install
git checkout starter
yarn dev

IV. Setting Up NextAuth

Create new file /src/pages/api/auth/[…nextauth].js:

import NextAuth, { NextAuthOptions } from 'next-auth';
import GoogleProvider from 'next-auth/providers/google';
import GithubProvider from 'next-auth/providers/github';

const options: NextAuthOptions = {
  providers: [
    GoogleProvider({
      clientId: process.env.NEXT_PUBLIC_GOOGLE_CLIENT_ID as string,
      clientSecret: process.env.NEXT_PUBLIC_GOOGLE_SECRET_ID as string,
    }),

    GithubProvider({
      clientId: process.env.NEXT_PUBLIC_GITHUB_CLIENT_ID,
     clientSecret: process.env.NEXT_PUBLIC_GITHUB_SECRET_ID,
    }),
  ],

  secret: process.env.NEXTAUTH_SECRET,
};

export default NextAuth(options);

This file contains NextAuth.js configs, every requests that is related to /api/auth/* such as: signIn, signOut ,… will be automatically handled by Next-Auth.js.

There are 2 Providers for our Sign in function: GoogleProvider and GithubProvider. These providers require clientId & secretID, we’ll get that later. Finally, to be able to use the hook useSession(), we need to get our component wrapped inside <SessionProvider /> at the top level of our application.

In src/pages/_app.js:

import 'antd/dist/antd.css';
import '../../styles/globals.css';
import type { AppProps } from 'next/app';
import { SessionProvider } from 'next-auth/react';

function MyApp({ Component, pageProps: { session, ...pageProps } }: AppProps) {
  return (
    <SessionProvider>
      <Component {...pageProps} />
    </SessionProvider>
  );
}

export default MyApp;

Instances useSession() containing session data and authentication status. <SessionProvider /> also helps our session updated and synced with the browser’s tabs & windows.

That’s all for setting everything up with NextAuth.js, let’s move on to get clientId, secretId of Google and Github.

V. ClientId and SecretId from Google

Go to Google Console Developers, choose Create Project:

Enter your project’s name and hit Create:

Open Credentials, choose Configure consent screen:

Choose External and hit Create button, fill out some basic information about your application, then hit Save and continue:

Choose Save and Continue:

Choose Save and Continue again:

After configuring all of that, open Credentials, click on Create Credentials, and then choose OAuth client ID to create our Client ID:

Choose Web Application:

There’re some information that we need to fill:

  • Application Name: your application name
  • Authorized Javascript origins: This is the application’s URL. Still, we are developing, just add http://localhost:3000. After deployment, we will have to come back here and add the application’s full URL.
  • Authorized redirects URIs: Users are redirected to this URL after they choose authentication with Google, just add http://localhost:3000/api/auth/callback/google

Then hit Create to get Client ID and Client Secret

VI. ClientId and SecretId from Github

Access Github, choose Settings:

Choose Developer settings in the Left Side Bar:

Choose New GitHub App:

Callback URL would be: http://localhost:3000/api/auth/callback/github

Choose Create GitHub App and get our client ID

Choose Generate a new client secret:

With clientID & secretID from Google and Github, create .env:

NEXT_PUBLIC_GOOGLE_CLIENT_ID=<google clientId key cua ban>
NEXT_PUBLIC_GOOGLE_SECRET_ID=<google secretId key cua ban>
NEXT_PUBLIC_GITHUB_CLIENT_ID=<github clientId key cua ban>
NEXT_PUBLIC_GITHUB_SECRET_ID=<github secretId key cua ban>

VII. Sign In, Sign out with NextAuth.js

In src/pages/login.tsx:

import { signIn } from 'next-auth/react';
...
	<Button
    block
    htmlType="submit"
    className="flex items-center justify-center"
    onClick={() => signIn('google', { callbackUrl: '/' })}
  >
    Login with Google 
		<GoogleOutlined className="text-red-600" />
  </Button>

...

	<Button
		block
		htmlType="submit"
		className="flex items-center justify-center"
		onClick={() => signIn('github', { callbackUrl: '/' })}
		>
		Login with GitHub
		<GithubFilled className="text-blue-900" />
	</Button>

signIn()’s first parameter is the name of our demand Provider. A second parameter is an object that includes a callback URL for redirecting users after signed in successfully.

In src/pages/shared/components/layout/PrivateLayout.tsx:

import { signOut } from 'next-auth/react';

...

<span onClick={() => signOut({ callbackUrl: '/' })}>Logout</span>

VIII. useSession to handle authentication logic

In src/pages/register.tsx:

const Home: NextPage = () => {
  const { status } = useSession();

  const { push } = useRouter();

  useEffect(() => {
    if (status === AuthenticationStatus.Authenticated) {
      push('/');
    }
  }, [status]);

  if (status !== AuthenticationStatus.Unauthenticated)
    return <Typography>Loading...</Typography>;

  return (
    <AuthLayout>
      <div className="flex flex-col items-center justify-center py-8 gap-8">
        <Typography className="text-2xl font-medium">
          Feture disabled
        </Typography>

        <Image
          src="/nextauth.png"
          width={600}
          height={314}
          className="rounded-lg shadow-lg"
        />
      </div>
    </AuthLayout>
  );
};

export default Home;

useSession() returns 1 object including data and status. data contains current logged in user’s information, if you are not authenticated, it will be undefined, status has 3 values: loading, authenticated, unauthenticated. We will have to check if a user is authentcated or not, if not, redirect them! Also, for a better UX, when status is loading return some loading component. And depend on authentication status, the layout for each status would be different:

const { status } = useSession();

if (status === AuthenticationStatus.Authenticated) {
	return (
		<PrivateLayout>
			<Content />
		</PrivateLayout>
	);	
}

return (
	<AuthLayout>
		<Content />
	</AuthLayout>
);

IX. Authentication Flow

There’re 3 kind of pages that we usually have: Required Authentication, Not Required AuthenticationNot Available When Authenticated.

Each page, each authentication status, we will have a different layout and different rights to access!

Therefore, we need to prepare a hoc (Higher Order Component) wrapped outside every page to handle the logic. For each type of page, we will have to:

  • Required Authentication: if users is not logged in, redirect them to ‘/’ (e.g: profile page)
  • Not Required Authentication: user can access this page with or without authentication, but the layout would be different depending on authentication status. (e.g: homepage).
  • Not Available When Authenticated: not accessible when logged in, which means we will have to redirect the user to the homepage page. (e.g: login page, register page).

Let’s create a hoc to handle it, in src/shared/hoc/withAuth.ts:

import { useSession } from 'next-auth/react';
import { useRouter } from 'next/router';
import { Typography } from 'antd';

export enum AuthenticationStatus {
  Loading = 'loading',
  Authenticated = 'authenticated',
  Unauthenticated = 'unauthenticated',
}

export enum AuthenticationType {
  Required,
  NotAccessibleWhenAuthenticated,
}

const withAuth = <P extends object>(
  Component: React.ComponentType<P>,
  authType: AuthenticationType = AuthenticationType.Required,
) =>
  function ({ ...props }: P) {
    function Authenticated({ ...props }: P) {
      const { status } = useSession();
      const { push } = useRouter();

      if (status === AuthenticationStatus.Loading)
        return <Typography>Loading...</Typography>;

      if (authType === AuthenticationType.Required) {
        if (status === AuthenticationStatus.Unauthenticated) {
          push('/');
          return <Typography>Loading...</Typography>;
        }

        return <Component {...(props as P)} />;
      }
      if (status === AuthenticationStatus.Authenticated) {
        push('/');
        return <Typography>Loading...</Typography>;
      }
      return <Component {...(props as P)} />;
    }

    return <Authenticated {...(props as P)} />;
  };

export default withAuth;

To use this hoc:

  • In Authentication required page:
import withAuth from 'src/shared/hoc/withAuth';
...
export default withAuth(ProfilePage);
  • In Not Accessible when authenticated page:
import withAuth, { AuthenticationType } from 'src/shared/hoc/withAuth';
...
export default withAuth(
  Login,
  AuthenticationType.NotAccessibleWhenAuthenticated,
);
  • In Not Required Authentication we just only have to check the current status and render the layout correctly.

Basically, we’ve completed our authentication flow to handle user’s access right of public route and private route. With withAuth() hoc, it’s much more convenient to scale up our project and remove a whole lot of repeating code.

X. Login with custom Credentials

After implementing with Google and Github providers, how about creating a custom Provider?

I prepare an API for Login (this is just a mock API, the value will be returned randomly): https://6249618c20197bb462722888.mockapi.io/api/mock/login

The response would be:

[
  {
    createdAt: '2022-04-02T13:55:40.641Z',
    name: 'Roxanne Skiles',
    avatar: 'https://cdn.fakercloud.com/avatars/aroon_sharma_128.jpg',
    email: '[email protected]',
    id: '1',
  },
],

To create Custom Provider, in src/pages/api/auth/[…nextauth].ts:

import CredentialsProvider from 'next-auth/providers/credentials';
import axios from 'axios';

...

CredentialsProvider({
  name: 'credentials',
  credentials: {
    email: { type: 'text' },
    password: { type: 'password' },
  },
  async authorize(credentials) {
    try {
      const response = await axios.post(
        'https://6249618c20197bb462722888.mockapi.io/api/mock/login',
        {
          email: credentials?.email,
          password: credentials?.password,
        },
      );
      return { ...response?.data, image: response?.data?.avatar };
    } catch (err) {
      console.log(err);
      throw err;
    }
  },
}),

We need to import CredentialsProvider, its parameters including:

  • name: Provider’s name, here will be *credentials*
  • credentials: is used to generate a form in NextAuth.js login page, we can add as many fields as we want to submit, in here we only need *username* and *password*
  • authorize: this function is used to handle authentication with the credential’s parameters, usually, we will call Login API here:
{
  email: credentials?.email,
  password: credentials?.password,
}
  • What is returned from authorize() will be included in user property of our JWT

After adding Custom Provider, we need to adjust a few things to match with the Next-Auth.js session data, to do that, we need to override Next-Auth.js signIn(), add callback property our options like this:

callbacks: {
    async signIn({ account, user }) {
      try {
        if (account?.provider === 'credentials') {
          if (typeof user?.access_token === 'string')
            account.id_token = user?.access_token;
          return true;
        } else return true;
      } catch (err) {
        throw err;
      }
    },
  },

To sign in with Custom Provider, in src/pages/login.tsx:

const onSubmitForm = ({
  password,
  email,
}: {
  email: string;
  password: string;
}) => {
  // HANDLE CUSTOM LOGIN HERE
  signIn('credentials', {
    email,
    password,
    redirect: false,
  });
};

We call signIn() with the first parameter is the name of our custom Provider(credentials), the second parameter will be the credentials payload passed to authorize() to handle the authentication, in here, we will have to pass email, password input value in our form. redirect is false to avoid redirecting to Next-Auth.js Sign In page (In here i’ve already created our Sign In page).

Since we’re using mock API so you can use any email or password. Click Login to login with our Custom Provider.

XI. Deploy to Vercel

To deploy our project, we need to add 2 more env variabled *NEXTAUTH_SECRET* & *NEXTAUTH_URL*

In src/pages/api/auth/[...nextAuth].ts:

const options: NextAuthOptions = {
  providers: [
    GoogleProvider({
      clientId: process.env.NEXT_PUBLIC_GOOGLE_CLIENT_ID as string,
      clientSecret: process.env.NEXT_PUBLIC_GOOGLE_SECRET_ID as string,
    }),
    GithubProvider({
      clientId: process.env.NEXT_PUBLIC_GITHUB_CLIENT_ID,
      clientSecret: process.env.NEXT_PUBLIC_GITHUB_SECRET_ID,
    }),
  ],
  secret: process.env.NEXTAUTH_SECRET, // ADD THIS LINE
};

NEXTAUTH_SECRET: simply go to **https://generate-secret.vercel.app/32** to get a random string.

NEXTAUTH_URL: Vercel will automatically config for us, so we don’t need to specify this.

Go to https://vercel.com/ , login, choose New Project:

Import our project:

Just remember to add all of those env variables we’ve used in our project: Google clientID, Google SecretId, Github clientID, Github secretID và *NEXTAUTH_SECRET*.

Hit Deploy button.

After deployed successfully, go back to Google & Github to add our application’s full url to enable Google & Github Authentication.

Hopefully, with this article, you can be able to build your own authentication flow with Next-Auth.js. Good luck!

Last but not least, please check out the NextAuth.js official document https://next-auth.js.org/getting-started/introduction 😄

Author
Enouvo

MORE FROM ENOUVO

TOGETHER

WE GROW