Implementing Authentication in a Next.js Application with NextAuth.js

Published On
Posted

A beginner's guide to the NextAuth.js authentication library and how to set it in a Next.js application.


NextAuth.js logo

NextAuth.js logo

Implementing authentication in Next.js applications can be achieved with the use of NextAuth.js, a flexible and easy-to-use authentication library designed specifically for Next.js. This guide will walk you through setting up NextAuth.js, managing user sessions, and securing routes in your Next.js application.

What is NextAuth.js and Why Use It?

NextAuth.js is a complete open-source authentication solution / library for Next.js applications based on standard Web APIs. It supports various authentication methods including email/password, OAuth providers like Google and GitHub, and even passwordless authentication. By using NextAuth.js, developers can implement robust authentication mechanisms without having to build them from scratch.

Setting Up NextAuth.js in a Next.js Application

To get started with NextAuth.js, follow these steps:

1. Install NextAuth.js:

First, add NextAuth.js to your project by running:

bash
npm install next-auth

2. Configure NextAuth.js:

Next, create a file [...nextauth].js in the app/api/auth directory that will serve as a route handler. This file will handle the authentication logic and configurations.

tsx
// app/api/auth/[...nextauth]/route.ts import NextAuth from "next-auth"; import { NextAuthOptions } from "next-auth"; import GoogleProvider from "next-auth/providers/google"; const authOptions: NextAuthOptions = { providers: [ GoogleProvider({ clientId: process.env.GOOGLE_CLIENT_ID as string, clientSecret: process.env.GOOGLE_CLIENT_SECRET as string, }), // Add more providers here ], // Add other configuration options here }; const handler = NextAuth(authOptions); export { handler as GET, handler as POST };

3. Set Up Environment Variables:

Store sensitive information like OAuth client IDs and secrets in environment variables. For example, if you are using Google, create a .env.local file in the root of your project like this:

plaintext
GOOGLE_CLIENT_ID=your-google-client-id GOOGLE_CLIENT_SECRET=your-google-client-secret NEXTAUTH_SECRET=your-nextauth-secret

4. Session Provider:

Wrap your application in the SessionProvider component to manage user sessions globally. Update your layout.tsx file:

tsx
// app/layout.tsx import { SessionProvider } from "next-auth/react"; export default function RootLayout({ children, }: { children: React.ReactNode; }) { return ( <html lang="en"> <body> <SessionProvider>{children}</SessionProvider> </body> </html> ); }

Managing User Sessions

NextAuth.js provides a hook, useSession, to access the session data in your components. This allows you to conditionally render UI based on the authentication status of the current user.

tsx
// app/components/NavBar.tsx import { useSession, signIn, signOut } from "next-auth/react"; function NavBar() { const { data: session } = useSession(); return ( <nav> {session ? ( <> <p>Signed in as {session.user?.email}</p> <button onClick={() => signOut()}>Sign out</button> </> ) : ( <button onClick={() => signIn()}>Sign in</button> )} </nav> ); } export default NavBar;

Implementing Secure Routes

Securing routes ensures that only authenticated users can access certain pages and parts of your application. This can be achieved using Next.js middleware or by checking the session status on individual pages.

1. Using Middleware:

Create a middleware.ts file to protect routes globally or selectively.

tsx
// middleware.ts import { withAuth } from "next-auth/middleware"; export default withAuth({ pages: { signIn: '/auth/signin', // Redirect to sign-in page if not authenticated }, }); export const config = { matcher: ['/protected-route'] }; // Protect specific routes

2. Checking Session on Individual Pages:

You can also check the session on individual pages and redirect unauthenticated users.

tsx
// app/protected/page.tsx import { getSession } from "next-auth/react"; import { redirect } from "next/navigation"; export default async function ProtectedPage() { const session = await getSession(); if (!session) { redirect('/auth/signin'); } return <p>Protected content</p>; }

Additional Functionalities of NextAuth.js

1. Providers:

NextAuth.js supports multiple authentication providers such as Google, GitHub, Twitter, and more. You can configure these providers in the authOptions object. Providers can be added by installing the required packages and configuring them as shown below:

typescript
import GitHubProvider from "next-auth/providers/github"; const authOptions: NextAuthOptions = { providers: [ GitHubProvider({ clientId: process.env.GITHUB_CLIENT_ID as string, clientSecret: process.env.GITHUB_CLIENT_SECRET as string, }), EmailProvider({ from: `onboarding@company.com`, generateVerificationToken() { return crypto.randomUUID(); }, async sendVerificationRequest({ request, url, identifier, provider, token }) { // Logic goes here ... }, }), // Other providers... ], };

2. Callbacks

NextAuth.js offers several callbacks that allow you to control the authentication flow. Examples include:

  • jwt: Customize the JWT token.
  • session: Modify the session object.
  • signIn: Run checks before sign-in.

Example:

typescript
const authOptions: NextAuthOptions = { // Providers and other options... callbacks: { async jwt({ token, user }) { if (user) { token.id = user.id; } return token; }, async session({ session, token }) { if (token) { session.user.id = token.id; } return session; }, }, };

3. Database Adapters:

NextAuth.js supports various database adapters for storing user data and sessions, including Prisma, TypeORM, and MongoDB. To use a database, configure the adapter and pass it to NextAuth.js:

typescript
import { PrismaAdapter } from "@next-auth/prisma-adapter"; import { PrismaClient } from "@prisma/client"; const prisma = new PrismaClient(); const authOptions: NextAuthOptions = { adapter: PrismaAdapter(prisma), providers: [ // Providers... ], };

4. Passwordless Authentication

NextAuth.js also supports passwordless authentication using email magic links. Configure an email provider and use it in the providers array:

typescript
import EmailProvider from "next-auth/providers/email"; const authOptions: NextAuthOptions = { providers: [ EmailProvider({ server: process.env.EMAIL_SERVER, from: process.env.EMAIL_FROM, }), ], };

5. Custom Sign-In Pages

NextAuth.js allows you to customize the sign-in page by providing a path to your custom page:

typescript
const authOptions: NextAuthOptions = { pages: { signIn: '/auth/custom-signin', }, // Other options... };

Common Issues and Solutions

  • Invalid Credentials: Ensure that the credentials provided to the OAuth providers or the database are correct. Use environment variables to store sensitive information securely.

  • Session Management: Use the SessionProvider at the top level of your application to manage session state effectively across your app.

  • Callback URLs: Configure the correct callback URLs for your OAuth providers to handle authentication responses properly.

By following these steps, you can set up a secure and efficient authentication system in your Next.js application using NextAuth.js, manage user sessions, and protect your routes effectively.

Conclusion

Implementing authentication in a Next.js application is greatly simplified with the use of NextAuth.js. This robust library not only supports multiple authentication methods but also provides extensive configuration options, making it adaptable to a variety of use cases. Whether you need OAuth support, passwordless authentication, or custom database integration, NextAuth.js offers the tools necessary to secure your application efficiently. By following best practices for setting up authentication, managing user sessions, and securing routes, you can ensure a seamless and secure user experience. With NextAuth.js, the complexity of authentication is handled, allowing you to focus on building and scaling your Next.js applications.

References and resources