Implementing Authentication in Next.js with NextAuth.js

Table of contents

No heading

No headings in the article.

Authentication is a crucial aspect of building web applications. It allows users to access secure areas of the application and ensures that sensitive data is only accessible to authorized individuals. Next.js provides several ways to implement authentication, including using popular authentication providers such as Auth0, Firebase, or NextAuth.js. In this article, we will explore how to implement authentication in Next.js using NextAuth.js, a popular authentication library that supports a wide range of authentication providers.

Getting Started with NextAuth.js

NextAuth.js is an open-source authentication library that provides a simple and flexible way to implement authentication in Next.js applications. It supports various authentication providers, including email and password, Google, Facebook, GitHub, and many more. To get started with NextAuth.js, you need to install it as a dependency in your Next.js application using the following command:

npm install next-auth

Once you have installed NextAuth.js, you can create an authentication endpoint in your Next.js API route by creating a new file in the pages/api/auth/[...nextauth].js directory with the following code:

import NextAuth from "next-auth";
import Providers from "next-auth/providers";

export default NextAuth({
  // Configure one or more authentication providers
  providers: [
    Providers.Email({
      server: process.env.EMAIL_SERVER,
      from: process.env.EMAIL_FROM,
    }),
    Providers.Google({
      clientId: process.env.GOOGLE_ID,
      clientSecret: process.env.GOOGLE_SECRET,
    }),
    // ...add more providers here
  ],
  // Database connection string (optional)
  database: process.env.DATABASE_URL,
});

In this code, we are importing NextAuth and Providers from the next-auth package. Next, we are creating an authentication endpoint by exporting a default function that calls NextAuth with a configuration object. This object specifies one or more authentication providers, such as email and password or Google, by passing a configuration object for each provider.

For example, the Providers.Email provider requires a server and from configuration to send email confirmation links to users. The Providers.Google provider requires a clientId and clientSecret configuration to authenticate users using Google's OAuth2 API. You can add more providers by following the same pattern and configuring them according to their respective provider's requirements.

Configuring Sessions and User Data

Once you have configured the authentication providers, you can configure how sessions and user data are stored and managed. NextAuth.js provides several options to configure sessions, such as setting the session cookie name, lifetime, and secure flag. You can also configure how user data is stored and accessed, such as using a database or a JSON file.

To configure sessions, you can pass a session object in the configuration object when calling NextAuth. For example, to set the session cookie name to myapp-session, you can add the following code to the configuration object:

session: {
  jwt: true,
  maxAge: 30 * 24 * 60 * 60, // 30 days
  cookie: {
    name: "myapp-session",
    secure: process.env.NODE_ENV === "production",
    sameSite: "strict",
    httpOnly: true,
  },
},

In this code, we are setting the session to use JSON Web Tokens (JWT) and setting the maximum session lifetime to 30 days. We are also setting the session cookie name to myapp-session, making it secure in production, setting the sameSite attribute to strict to prevent cross-site request forgery (CSRF) attacks, and making the cookie httpOnly to prevent client-side JavaScript from accessing it.

To configure user data storage, you can pass a database object in the configuration object when calling NextAuth. For example, to use a MongoDB database to store user data, you can add the following code to the configuration object:

database: {
  type: "mongodb",
  useNewUrlParser: true,
  url: process.env.MONGODB_URI,
},

In this code, we are specifying the database type as MongoDB and passing the connection string as the url property. You can use other databases, such as MySQL, PostgreSQL, or SQLite, by following the respective database configuration options.

Protecting Routes with Authentication

Now that you have set up authentication in your Next.js application, you can protect routes that require authentication by checking the session data or user data. NextAuth.js provides a helper function called getSession to retrieve the session data in your pages or API routes. You can use this function to check if the user is authenticated and redirect them to the login page if they are not.

For example, to protect a route that requires authentication, you can create a new file in the pages/protected.js directory with the following code:

import { getSession } from "next-auth/client";
import Layout from "../components/Layout";

export default function Protected({ user }) {
  if (!user) {
    return <Layout>You need to be authenticated to access this page</Layout>;
  }
  return (
    <Layout>
      <h1>Welcome, {user.email}</h1>
    </Layout>
  );
}

export async function getServerSideProps(context) {
  const session = await getSession(context);
  return {
    props: {
      user: session?.user ?? null,
    },
  };
}

In this code, we are importing the getSession function from the next-auth/client package and using it to retrieve the session data in the getServerSideProps function. We are then passing the user object to the Protected component as a prop and checking if it exists. If the user is not authenticated, we are returning a message to inform them that they need to log in to access the page. If the user is authenticated, we are displaying their email address in a heading.

NextAuth.js also provides a client-side library that you can use to fetch session data and manage authentication in your client-side code. The library exposes a set of hooks that you can use to retrieve session data and perform authentication-related actions, such as logging in or logging out.

For example, to display the user's email address in your application's header, you can use the useSession hook provided by NextAuth.js. Here is an example code snippet:

import { useSession } from "next-auth/client";

export default function Header() {
  const [session, loading] = useSession();

  return (
    <header>
      {loading && <p>Loading...</p>}
      {session && <p>Welcome, {session.user.email}</p>}
      {!session && <p>Please sign in</p>}
    </header>
  );
}

In this code, we are importing the useSession hook from the next-auth/client package and using it to retrieve the session data in the Header component. We are then displaying a message to inform the user that they need to sign in if they are not authenticated or displaying their email address if they are authenticated.

Using the client-side library provided by NextAuth.js can help simplify your client-side code and provide a consistent way to manage authentication-related actions across your application.

Another feature of NextAuth.js worth mentioning is its support for custom providers. This allows you to implement authentication with any provider that is not supported out of the box by NextAuth.js. Custom providers can be implemented by defining a set of callbacks that handle the authentication flow, such as fetching user data, creating user accounts, or logging in users.

To implement a custom provider, you can define a configuration object with the following properties:

  • id: A unique identifier for the provider

  • name: The display name of the provider

  • type: The type of the provider (e.g., OAuth, email, credentials)

  • callbacks: An object that defines the authentication flow callbacks

Here is an example configuration object for a custom provider that uses OAuth authentication with GitHub:

import { NextAuth } from "next-auth";
import Providers from "next-auth/providers";

export default NextAuth({
  providers: [
    Providers.OAuth({
      id: "github",
      name: "GitHub",
      type: "oauth",
      version: "2.0",
      scope: "read:user",
      clientId: process.env.GITHUB_CLIENT_ID,
      clientSecret: process.env.GITHUB_CLIENT_SECRET,
      authorizationUrl: "https://github.com/login/oauth/authorize",
      accessTokenUrl: "https://github.com/login/oauth/access_token",
      profileUrl: "https://api.github.com/user",
      async profile(profile, accessToken) {
        return {
          id: profile.id,
          name: profile.name,
          email: profile.email,
          image: profile.avatar_url,
          accessToken,
        };
      },
    }),
  ],
});

In this code, we are defining a custom provider with the id property set to "github" and the name property set to "GitHub". We are using the OAuth provider from the next-auth/providers package to configure the OAuth flow with GitHub.

The profile function is a callback that is called when the user is authenticated. In this function, we are mapping the user data returned by GitHub's API to a standard user object with an id, name, email, image, and accessToken property.

Custom providers can be useful when you need to implement authentication with a provider that is not supported by NextAuth.js out of the box or when you need to implement a custom authentication flow that is not provided by the built-in providers.

NextAuth.js is a powerful and flexible authentication library for Next.js that provides support for various authentication providers and allows you to implement custom providers as well. By using NextAuth.js, you can implement authentication in your Next.js applications quickly and easily, allowing you to focus on building your application's features and functionality.

Serverless Deployment using Vercel and AWS Lambda

NextAuth.js also provides support for serverless deployment platforms like Vercel and AWS Lambda. This allows you to deploy your Next.js application with NextAuth.js to a serverless environment, which can be beneficial in terms of scalability and cost-effectiveness.

When deploying your Next.js application with NextAuth.js to a serverless environment, you need to make sure that the environment variables required for authentication providers, such as API keys and secrets, are securely stored and accessed.

NextAuth.js provides a simple way to store and access environment variables by using the publicRuntimeConfig and serverRuntimeConfig properties of the Next.js configuration object. The publicRuntimeConfig property is used for environment variables that are accessible on the client-side, while the serverRuntimeConfig property is used for environment variables that are only accessible on the server-side.

Here is an example Next.js configuration object that uses publicRuntimeConfig and serverRuntimeConfig to store and access environment variables:

module.exports = {
  publicRuntimeConfig: {
    NEXTAUTH_URL: process.env.NEXTAUTH_URL,
  },
  serverRuntimeConfig: {
    MY_SECRET: process.env.MY_SECRET,
  },
};

In this code, we are defining a configuration object that uses publicRuntimeConfig to store the NEXTAUTH_URL environment variable, which is used by NextAuth.js to construct callback URLs for authentication providers. We are using serverRuntimeConfig to store the MY_SECRET environment variable, which is only accessible on the server-side.

By using the publicRuntimeConfig and serverRuntimeConfig properties of the Next.js configuration object, you can securely store and access environment variables required for authentication providers when deploying your Next.js application with NextAuth.js to a serverless environment.

Overall, NextAuth.js is a powerful and flexible authentication library for Next.js that provides support for various authentication providers and allows you to implement custom providers as well. With its support for serverless deployment platforms, you can easily deploy your Next.js application with NextAuth.js to a scalable and cost-effective serverless environment.

Did you find this article valuable?

Support Oluwatosin Gbenga by becoming a sponsor. Any amount is appreciated!