Kingsley Ubah 21. Web Developer. Technical Writer. African in Tech.

How to implement authentication and authorization in Next.js

10 min read 2982

Implementing User Authorization Next Js

Authentication and authorization are two important steps in protecting your users’ most valuable information, however, developers often confuse the two.

In this tutorial, we’ll learn how to implement a few different authentication strategies in Next.js, for example, using credentials like email and password, as well as GitHub, a third-party service. We’ll also cover implementing authorization using the role-based authorization system in Next.js. Let’s get started!

Jump ahead

Authentication vs. authorization in Next.js

Authentication is the act of validating that a user is who they claim to be. Usernames and passwords are the most common authentication factors.

When authenticating a user, if the user enters the correct data, the server assumes that the identity is valid and grants the user access to the server resource.

On the other hand, authorization in a security system is the process of giving the user permission to access a specific resource or function on the server. This term is often used interchangeably with access control or client privilege.

Usually, authentication precedes authorization; users should first prove that their identities are genuine before the backend administrator grants them access to the requested resources.

In this article, we’ll implement both authentication and authorization strategies using NextAuth.js. Out of the box, NextAuth.js supports several different authentication providers, including Atlassian, Auth0, and GitHub. You can check out the docs for a complete list.

Since this article covers both authentication and authorization, we’ll start by learning about authentication, which generally comes first. Building on this knowledge, we’ll then learn how to implement authorization using NextAuth.js.

Prerequisites

To follow along with this article, you’ll need the following:

  • The latest or LTS version of Node.js installed on your system, which is Node.js v18 at the time of writing
  • Basic knowledge of JavaScript
  • Basic knowledge of React
  • Basic knowledge of Next.js; familiarity with Next.js API routes is an added bonus
  • Familiarity with building APIs

Next.js authentication with NextAuth.js

As previously mentioned, NextAuth.js is a robust and feature-packed authentication package for Next.js. In this section, we’ll implement authentication using two separate methods, GitHub and email and password credentials.

To get started, clone this repository to get the starter file. Now, from the app directory, run the following code to bootstrap the application:

// install dependencies 
npm install

// start dev-server
npm run dev

If everything was successful, you should see the following:

Nextjs Tailwind Starter Application

The application boilerplate above is a simple Next.js and Tailwind CSS application. Going forward, we’ll build on top of this template.

Authentication using GitHub OAuth

First, install NextAuth.js by running the code below:

npm i next-auth

To add NextAuth.js to your application, create a [...nextauth].js file in the pages/api/auth directory and add the following code:

import NextAuth from "next-auth"
import GithubProvider from "next-auth/providers/github"

export const authOptions = {
  secret: process.env.NextAuth_SECRET,
  // Configure one or more authentication providers
  providers: [
    GithubProvider({
      clientId: process.env.GITHUB_ID,
      clientSecret: process.env.GITHUB_SECRET,
    }),
    // ...add more providers here
  ],
}

export default NextAuth(authOptions);

The code above configures NextAuth.js with the GitHub provider. So, to get your client ID and client secret, you’ll need to create a new GitHub OAuth application.

Once this is done, create a .env.local file in the root directory with the following code:

GITHUB_ID=<!-- Your client ID -->
GITHUB_SECRET=<!-- Your client secret -->
NextAuth_SECRET=<!-- any text -->

Now, NextAuth.js will automatically handle all requests to /api/auth/*, including signIn, callback, signOut, etc.



Configure shared state

To configure the shared state, we need to expose the session context <SessionProvider /> at the top level of your application. Doing so will enable the use of the useSection() Hook from React.

<SessionProvider /> keeps the session updated and synced between browser tabs and windows. So, to expose the session context, update the page/_api component as shown below:

import '../styles/globals.css'
import { SessionProvider } from "next-auth/react"
export default function App({
  Component,
  pageProps: { session, ...pageProps },
}) {
  return (
    <SessionProvider session={session}>
      <Component {...pageProps} />
    </SessionProvider>
  )
}

With the setup above, instances of useSession will have access to the session data and status. Consequently, we can check if a user is authenticated or authorized from the session and dynamically render jsx to the user; we’ll see this in action later.

Next, update the page/index.js component, as shown below:

import { useSession, signIn, signOut } from "next-auth/react"

export default function Home() {
  const { data: session, status } = useSession()
  if (status === "authenticated") {
    return (
      <section className="grid h-screen place-items-center">
        <div className="max-w-sm p-6 bg-white border border-gray-200 rounded-lg shadow dark:bg-gray-800 dark:border-gray-700">
          <h2 className="mb-2 text-2xl font-bold tracking-tight text-gray-900 dark:text-white">Hi {session?.user?.name}</h2><br />
          <p className="mb-3 font-normal text-gray-700 dark:text-gray-400">You are signed in as {session?.user?.email}.</p>
          <button
            type="button"
            onClick={() => signOut()}
            className="inline-flex items-center px-3 py-2 text-sm font-medium text-center text-white bg-red-700 rounded-lg hover:bg-red-800 focus:ring-4 focus:outline-none focus:ring-red-300 dark:bg-red-600 dark:hover:bg-red-700 dark:focus:ring-red-800">
            Logout
          </button>
        </div>
      </section>
    )
  }
  return (
    <section className="grid h-screen place-items-center">
      <div className="max-w-sm p-6 bg-white border border-gray-200 rounded-lg shadow dark:bg-gray-800 dark:border-gray-700">
        <h2 className="mb-2 text-2xl font-bold tracking-tight text-gray-900 dark:text-white">Welcome To LogRocket</h2><br />
        <p className="mb-3 font-normal text-gray-700 dark:text-gray-400">You currently not authenticated. Click the login button to get started!</p>
        <button
          type="button"
          onClick={() => signIn()}
          className="inline-flex items-center px-3 py-2 text-sm font-medium text-center text-white bg-green-700 rounded-lg hover:bg-green-800 focus:ring-4 focus:outline-none focus:ring-green-300 dark:bg-green-600 dark:hover:bg-green-700 dark:focus:ring-green-800">
          Login
        </button>
      </div>
    </section>
  );
}

In the code above, we destructured data and renamed it session and status from the return value of useSession(). Using the status value, we render the appropriate UI depending on the authentication status of a user.

Now, if you restart the dev-server, you’ll see the following in your browser:

Become Authenticated NextAuthjs

The UI above is shown because you are not currently authenticated. To become authenticated, click the Login button, and you’ll see the default login page created by NextAuth.js, which contains only the GitHub provider:

Authentication Signin GitHub

After clicking the button, GitHub may ask you to authorize the app. Finally, if everything is successful, you’ll see the following:

Authentication Successful GitHub

Authentication using credentials

In this section, we’ll learn how to implement authentication using email and password credentials. To do so, we need a database and some backend logic to handle our login request. But, this is out of the scope of this article, so we’ll improvise by using Next.js API routes.

In the root directory, create a data folder, and inside, create a users.js file with the following code:

const Users = [
    { id: "111", name: "John Doe", email: "[email protected]", password: 1232, role: "user" },
    { id: "112", name: "Jane Doe", email: "[email protected]", password: 1234, role: "user" },
    { id: "113", name: "Jenny Doe", email: "[email protected]", password: 1235, role: "admin" },
    { id: "114", name: "Jude Doe", email: "[email protected]", password: 2222, role: "admin" },
];
export { Users };

In the code above, we export an array of users that will act as our database. Now, in the page/api folder, create a login.js file with the following code:

 import { Users } from "../../data/users";
export default function handler(req, res) {
    try {
        if (req.method !== 'POST') {
            res.status(405).send({ message: 'Only POST requests allowed' })
            return
        }
        const body = JSON.parse(JSON.stringify(req.body))
        const user = Users.find((user) => user.email === body.email && user.password === parseInt(body.password));
        if (!user) {
            res.status(404).send({ message: 'User does not exit!' })
            return
        }

        res.status(200).json(user);
    } catch (error) {
        res.status(405).send({ message: `{error.message}` })
        return
    }
};

The Node.js code above represents the Next.js API, which is the backend code that will handle our login request. It finds and returns a user from the users array using the credentials provided.

Next, update the [...nextauth].js file as shown below:

import NextAuth from "next-auth"
import GithubProvider from "next-auth/providers/github"
import CredentialsProvider from "next-auth/providers/credentials";
export const authOptions = {
    secret: process.env.NextAuth_SECRET,
    // Configure one or more authentication providers
    providers: [
        GithubProvider({
            clientId: process.env.GITHUB_ID,
            clientSecret: process.env.GITHUB_SECRET,
        }),
        CredentialsProvider({
            // The name to display on the sign in form (e.g. "Sign in with...")
            name: "Credentials",
            // `credentials` is used to generate a form on the sign in page.
            // You can specify which fields should be submitted, by adding keys to the `credentials` object.
            // e.g. domain, username, password, 2FA token, etc.
            // You can pass any HTML attribute to the <input> tag through the object.
            credentials: {
                email: {
                    label: "Email",
                    type: "text",
                    placeholder: "Enter email",
                },
                password: {
                    label: "Password",
                    type: "password",
                    placeholder: "Enter Password",
                },
            },

            async authorize(credentials, req) {
                const { email, password } = credentials
                const res = await fetch("http://localhost:3000/api/login", {
                    method: "POST",
                    headers: {
                        "Content-Type": "application/json",
                    },
                    body: JSON.stringify({
                        email,
                        password,
                    }),
                });
                const user = await res.json();
                if (res.ok && user) {
                    return user;
                } else return null;
            },
        }),
        // ...add more providers here
    ],
    callbacks: {
        async jwt({ token, user }) {
            return { ...token, ...user };
        },
        async session({ session, token, user }) {
            // Send properties to the client, like an access_token from a provider.
            session.user = token;
            return session;
        },
    },
    pages: {
        signIn: '/auth/signin',
    }
}
export default NextAuth(authOptions)

In the [...nextauth].js code above, we added the CredentialsProvider and configured it to authenticate users with email and password. In the authorize async function, we handled the POST request made to the Next.js API routes we created earlier using the specified user credentials. Then, we returned the user if the user was found, or null if the user was not found.

We then handled specific actions using callbacks. In NextAuth.js, callbacks are powerful, async functions that enable us to control what happens when an action occurs. Callbacks enable us to implement access control when we’re not using a database, as is our current case. They also enable us to integrate with external databases or APIs.

In the code above, we used a JWT callback to return a new authenticated user object with its token and a session callback to make the new user with the JWT token available to the client.

Finally, we used the pages property to overwrite the Next.js default login page by specifying a signin property with the /auth/signin value.

Now, in the pages folder, create a new folder called auth. Inside the auth folder, create a new file called signin.js with the following code:

import { useRef } from "react";
import { getProviders, getSession, signIn } from "next-auth/react"

const Signin = ({ providers }) => {
    const email = useRef("");
    const password = useRef("");
    return (
        <div className="flex items-center min-h-screen p-4 bg-gray-100 lg:justify-center">
            <div
                className="flex flex-col overflow-hidden bg-white rounded-md shadow-lg max md:flex-row md:flex-1 lg:max-w-screen-md"
            >
                <div
                    className="p-4 py-6 text-white bg-blue-500 md:w-80 md:flex-shrink-0 md:flex md:flex-col md:items-center md:justify-evenly"
                >
                    <div className="my-3 text-4xl font-bold tracking-wider text-center">
                        <a href="#">LOGROCKET</a>
                    </div>
                    <p className="mt-6 font-normal text-center text-gray-300 md:mt-0">
                        Lorem Ipsum is simply dummy text of the printing and typesetting industry.
                    </p>
                    <p className="flex flex-col items-center justify-center mt-10 text-center">
                        <span>Don't have an account?</span>
                        <a href="#" className="underline">Get Started!</a>
                    </p>
                    <p className="mt-6 text-sm text-center text-gray-300">
                        Read our <a href="#" className="underline">terms</a> and <a href="#" className="underline">conditions</a>
                    </p>
                </div>
                <div className="p-5 bg-white md:flex-1">
                    <h3 className="my-4 text-2xl font-semibold text-gray-700">Account Login</h3>
                    <form action="#" className="flex flex-col space-y-5">
                        <div className="flex flex-col space-y-1">
                            <label htmlFor="email" className="text-sm font-semibold text-gray-500">Email address</label>
                            <input
                                type="email"
                                id="email"
                                autoFocus
                                className="px-4 py-2 transition duration-300 border border-gray-300 rounded focus:border-transparent focus:outline-none focus:ring-4 focus:ring-blue-200"
                                onChange={(e) => (email.current = e.target.value)}
                            />
                        </div>
                        <div className="flex flex-col space-y-1">
                            <div className="flex items-center justify-between">
                                <label htmlFor="password" className="text-sm font-semibold text-gray-500">Password</label>
                                <a href="#" className="text-sm text-blue-600 hover:underline focus:text-blue-800">Forgot Password?</a>
                            </div>
                            <input
                                type="password"
                                id="password"
                                className="px-4 py-2 transition duration-300 border border-gray-300 rounded focus:border-transparent focus:outline-none focus:ring-4 focus:ring-blue-200"
                                onChange={(e) => (password.current = e.target.value)}
                            />
                        </div>
                        <div>
                            <button
                                type="button"
                                className="w-full px-4 py-2 text-lg font-semibold text-white transition-colors duration-300 bg-blue-500 rounded-md shadow hover:bg-blue-600 focus:outline-none focus:ring-blue-200 focus:ring-4"
                                onClick={() => signIn("credentials", {
                                    email: email.current, password: password.current,
                                })}
                            >
                                Log in
                            </button>
                        </div>
                        <div className="flex flex-col space-y-5">
                            <span className="flex items-center justify-center space-x-2">
                                <span className="h-px bg-gray-400 w-14"></span>
                                <span className="font-normal text-gray-500">or login with</span>
                                <span className="h-px bg-gray-400 w-14"></span>
                            </span>
                            <div className="flex flex-col space-y-4">
                                {providers &&
                                    Object.values(providers).map(provider => {
                                        if (provider.name !== "Credentials") {
                                            return (
                                                <div key={provider.name} style={{ marginBottom: 0 }}>
                                                    <a
                                                        href="#"
                                                        className="flex items-center justify-center px-4 py-2 space-x-2 transition-colors duration-300 border border-gray-800 rounded-md group hover:bg-gray-800 focus:outline-none"
                                                        onClick={() => signIn(provider.id)}
                                                    >
                                                        <span>
                                                            <svg
                                                                className="w-5 h-5 text-gray-800 fill-current group-hover:text-white"
                                                                viewBox="0 0 16 16"
                                                                version="1.1"
                                                                aria-hidden="true"
                                                            >
                                                                <path
                                                                    d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z"
                                                                ></path>
                                                            </svg>
                                                        </span>
                                                        <span className="text-sm font-medium text-gray-800 group-hover:text-white">Sign in with{' '} {provider.name}</span>
                                                    </a>
                                                </div>
                                            )
                                        }
                                    })}
                            </div>
                        </div>
                    </form>
                </div>
            </div>
        </div>
    )
}
export default Signin
export async function getServerSideProps(context) {
    const { req } = context;
    const session = await getSession({ req });
    const providers = await getProviders()
    if (session) {
        return {
            redirect: { destination: "/" },
        };
    }
    return {
        props: {
            providers,
        },
    }
}

The code above simply displays a login form to enable login using credentials. Below this form, it displays a list of other providers. Currently, we have only the GitHub provider.

If the login attempt is successful, the user will be redirected to the homepage. Now, if you restart the dev-server, you should see the following:

Sign In Email Password Credentials

You can test the credentials provider implementation by logging in with a username and password from the data array, and the result will be the same as the GitHub provider. With that, our authentication is complete!

In the next section, we’ll learn how to implement authorization.

Next.js authorization with NextAuth.js

Authorization checks whether an authenticated user has permission to view a UI or use a feature. In this section, we’ll implement the role-based authorization system. To do so, we need to create a page that requires admin privileges to view. In the page folder, create a dashboard.js file with the following code:

import { signOut, useSession } from "next-auth/react"

export default function Dashboard() {
    const { data: session } = useSession()
    const user = session?.user;
    if (user?.role !== "admin") {
        return (
            <section className="grid h-screen place-items-center">
                <div className="w-25">
                    <p>You do not have permission to view this page!</p>
                </div>
            </section>
        );
    }
    return (
        <section className="grid h-screen place-items-center">
            <div className="max-w-sm p-6 bg-white border border-gray-200 rounded-lg shadow dark:bg-gray-800 dark:border-gray-700">
                <h2 className="mb-2 text-2xl font-bold tracking-tight text-gray-900 dark:text-white">Hello {session?.user?.name}</h2><br />
                <p className="mb-3 font-normal text-gray-700 dark:text-gray-400">You are an admin user currently signed in as {session?.user?.email}.</p>
                <button
                    type="button"
                    onClick={() => signOut()}
                    className="inline-flex items-center px-3 py-2 text-sm font-medium text-center text-white bg-red-700 rounded-lg hover:bg-red-800 focus:ring-4 focus:outline-none focus:ring-red-300 dark:bg-red-600 dark:hover:bg-red-700 dark:focus:ring-red-800">
                    Logout
                </button>
            </div>
        </section>
    )
}

The code above gets the currently logged-in and authenticated user from the session and checks if the user role is admin. If it is, it renders the page. Otherwise, it renders a UI reading you do not have permission to view this page!.

You can test this out by logging in with a user that is not an admin and navigating to localhost:3000/dashboard. You’ll see the following:

Authorization Nextauth JS

Now, log in with an admin user and visit the dashboard. You’ll see the following:

Authorization Success NextAuthjs

Conclusion

The NextAuth.js library provides built-in support for many popular sign-in services, making the process of API integration quick and easy. In this article, we explored using NextAuth.js to configure a Next.js application to use the OAuth flow for user authorization. We also explored authenticating and authorizing users using email and password credentials in NextAuth.js.

I hope you enjoyed this article. If you have any questions, be sure to leave a comment. Happy coding!

LogRocket: Full visibility into production Next.js apps

Debugging Next applications can be difficult, especially when users experience issues that are difficult to reproduce. If you’re interested in monitoring and tracking state, automatically surfacing JavaScript errors, and tracking slow network requests and component load time, try LogRocket.

LogRocket is like a DVR for web and mobile apps, recording literally everything that happens on your Next.js app. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred. LogRocket also monitors your app's performance, reporting with metrics like client CPU load, client memory usage, and more.

The LogRocket Redux middleware package adds an extra layer of visibility into your user sessions. LogRocket logs all actions and state from your Redux stores.

Modernize how you debug your Next.js apps — .

Kingsley Ubah 21. Web Developer. Technical Writer. African in Tech.

3 Replies to “How to implement authentication and authorization in Next.js”

  1. Does this have the problem of auth state initially not being available on page load, so that it takes a second for it to switch to authenticated mode?

    For me, if I get auth on the server, on the client the session is initially null. So the benefit of having gotten the session on the server is lost.

    1. Hi,

      I’m having a little difficulty understanding your question but I’m guessing that you’re asking why the auth state is not available on initial page load.

      Regarding that, note that a user’s GitHub information is only retrieved after the user is only retrieved after the user has authorized the app to get said information. On initial load, the page simply shows a dummy profile picture and a text instruction the user to login in with GitHub.

      Hope it helps!

      Kingsley Ubah,
      Author at LogRocket.

Leave a Reply