Implementing Client and Server-Side Authentication with Auth.js

Andry Dina
Authjs and Next.js

Introduction

As web development continues to advance, it's crucial to implement secure and convenient user authentication. Next.js, a popular React framework, provides server-side rendering and simplifies development. By integrating Next.js with Auth.js for authentication and Prisma for data management, you can build powerful and scalable applications. This guide demonstrates how to set up both client-side and server-side authentication in a Next.js app router using these technologies.

๐Ÿ’ก What is Auth.js?

Auth.js (previously known as NextAuth.js) is a versatile authentication library designed for Next.js applications. It provides seamless support for various authentication methods, including OAuth providers (e.g., GitHub, Google), email sign-ins, and custom authentication strategies, all while handling token management and security behind the scenes.

How Does Auth.js Work?

Auth.js and Prisma work together to manage user sessions, authentication, and database interactions. Auth.js handles the flow of authentication, while Prisma is an ORM (Object-Relational Mapping) that connects your app to the database, checking for data integrity and security during interactions.

๐Ÿ“ƒ Requirements

To successfully use Auth.js with Prisma in a Next.js application, you'll need:

  • Next.js: The React framework used for building server-side and client-side rendered applications.
  • Server Actions in Next.js: Provides server-side capabilities to handle backend logic.
  • Prisma: An ORM for database migrations and access.
  • Auth.js: Handles authentication through various methods (e.g., GitHub OAuth).
  • TypeScript: Adds static typing to JavaScript for better code quality and maintainability.

Initializing Shadcn

Step 1: Initialize Shadcn

Shadcn UI is a component library designed to work with React and Next.js. You will be prompted to configure components.json. After that, you can start adding components to your project with commands like:

1npx shadcn@latest init
2npx shadcn@latest add button

Setting Up Authentication with Auth.js

For more information on how to install shadcn, see How to Install shadcn in a Next.js Project: Step-by-Step Guide

Step 1: Install Auth.js

To install Auth.js, use the following command:

1npm install next-auth@beta

Step 2: Generate a Secret

The only crucial environment variable you need is AUTH_SECRET. This random value serves as the foundation for encrypting tokens and verifying emails. The official Auth.js CLI provides a tool to generate this value.:

1npx auth secret

Depending on the platform you're integrating with, set up the callback URL in your platform account. For example, if you're integrating with Google and GitHub, input the two respective callback URLs in the configuration interface.

1//For Google:
2https://{YOUR_DOMAIN}/api/auth/callback/google
3
4//For Github:
5https://{YOUR_DOMAIN}/api/auth/callback/github

In development mode, use localhost:3000for the domain. To set up callback URL processing:

Step 3: Create an Auth Route Handler

Create a new auth.ts at at the root of your project with this code:

1// ./auth.ts
2import NextAuth from 'next-auth';
3
4const authOptions = {
5  providers: [...],
6  secret: process.env.AUTH_SECRET,
7};
8
9export const { handlers, auth, signIn, signOut } =  NextAuth(authOptions);

Add middleware to keep the session alive and update its expiration.

1export { auth as middleware } from '@/auth'

Add a route handler under /app/api/auth/[...nextauth]/route.ts:

1// app/api/auth/[...nextauth]/route.ts
2
3import { handlers } from '@/auth'
4export const { GET, POST } = handlers

Environment Variables in Auth.js

Add the necessary environment variables in a .env file at the root of your project:

1AUTH_GITHUB_ID=your-github-auth-id
2AUTH_GITHUB_SECRET=your-github-key-secret
3AUTH_GOOGLE_ID=your-google-auth-id
4AUTH_GOOGLE_SECRET=your-google-key-secret
5AUTH_SECRET=your-auth-secret
6NEXTAUTH_URL=http://localhost:3000

Providers Authentication with Auth.js

To use multiple providers, configure them in auth.tsand set callback URLs:

Developers can set specific options in the authorization parameters to always request a Refresh Token upon sign-in. However, this will prompt users to confirm access every time they sign in.

Note: If an app requires the Refresh Token or Access Token for a Google account without using a database to store user credentials, these steps may be necessary.

1// auth.ts
2import { AuthOptions } from 'next-auth';
3import Google from 'next-auth/providers/google';
4import Github from 'next-auth/providers/github';
5
6const authOptions: AuthOptions = {
7  providers: [
8    Google({
9      authorization: {
10        params: {
11          prompt: 'consent',
12          access_type: 'offline',
13          response_type: 'code',
14        },
15      },
16    }),
17    Github,
18  ],
19  callbacks: {
20    async signIn({ user, account, profile, email, credentials }) {
21      return true;
22    },
23  },
24};
25
26export const { handlers, auth, signIn, signOut } =  NextAuth(authOptions);

Install and Initialize Prisma

Step 1: Install Prisma

To get started, install Prisma:

1npm install @prisma/client @auth/prisma-adapter
2npm install prisma --save-dev

To connect to your database and fetch data, Prisma requires the DATABASE_URL environment variable. This variable allows Prisma to establish the connection and retrieve the necessary information.

1DATABASE_URL=postgresql://USER:PASSWORD@HOST:PORT/DATABASE?schema=SCHEMA

To optimize Prisma ORM performance, a single Prisma instance should be created for the entire project. Import this instance as needed, preventing multiple instances from being created every time PrismaClient is utilized. Finally, import the Prisma instance from the configuration in the auth.ts file.

1import { PrismaClient } from '@prisma/client'
2
3const globalForPrisma = globalThis as unknown as { prisma: PrismaClient }
4
5export const prisma = globalForPrisma.prisma
6  ? globalForPrisma.prisma
7  : new PrismaClient();
8
9if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma

Update your ./auth.ts

1import NextAuth from 'next-auth';
2import { PrismaAdapter } from '@auth/prisma-adapter'
3import { prisma } from '@/prisma'
4
5export const { handlers, auth, signIn, signOut } = NextAuth({
6  adapter: PrismaAdapter(prisma),
7  providers: [...],
8})

Step 2: Initialize Prisma

Initialize Prisma with the following command:

1npx prisma init

Step 3: Configure Prisma Schema

To use Prisma with Next.js and Auth.js, you need to define your database schema. Here's an example of a schema file (prisma/schema.prisma) that supports authentication using various providers.
Create a schema file at prisma/schema.prisma with the following models:

1datasource db {
2  provider = 'postgresql'
3  url = env('DATABASE_URL')
4}
5
6generator client {
7  provider = 'prisma-client-js' 
8}
9
10model User {
11  id String @id @default(cuid())
12  name String?
13  email String @unique
14  emailVerified DateTime?
15  image String?
16  accounts Account[]
17  sessions Session[]
18  // Optional for WebAuthn support
19  Authenticator Authenticator[]
20
21  createdAt DateTime @default(now())
22  updatedAt DateTime @updatedAt
23}
24
25model Account {
26  userId String
27  type String
28  provider String
29  providerAccountId String
30  refresh_token String?
31  access_token String?
32  expires_at Int?
33  token_type String?
34  scope String?
35  id_token String?
36  session_state String?
37
38  createdAt DateTime @default(now())
39  updatedAt DateTime @updatedAt
40
41  user User @relation(fields: [userId], references: [id], onDelete: Cascade)
42
43  @@id([provider, providerAccountId])
44}
45
46model Session {
47  sessionToken String @unique
48  userId String
49  expires DateTime
50  user User @relation(fields: [userId], references: [id], onDelete: Cascade)
51
52  createdAt DateTime @default(now())
53  updatedAt DateTime @updatedAt
54}
55
56model VerificationToken {
57  identifier String
58  token String
59  expires DateTime
60
61  @@id([identifier, token])
62}
63
64// Optional for WebAuthn support
65model Authenticator {
66  credentialID String @unique
67  userId String
68  providerAccountId String
69  credentialPublicKey String
70  counter Int
71  credentialDeviceType String
72  credentialBackedUp Boolean
73  transports String?
74
75  user User @relation(fields: [userId], references: [id], onDelete: Cascade)
76
77  @@id([userId, credentialID])
78}

Step 4: Run Prisma Migrate

Apply the schema with:

1npx prisma migrate dev

This will generate and run an SQL migration file.

Note that you will need to specify your database connection string in the environment variable DATABASE_URL. You can do this by setting it in a .env file at the root of your project. Note Prisma doesnโ€™t support .env.local syntax, it must be named .env.

Generate Prisma Client

You can use this command to generate the prisma client

1npx prisma generate

Step 5: Use Prisma Studio

To view and edit data, use Prisma Studio:

1npx prisma studio

๐Ÿ‘Great! You now have the ability to generate and display a comprehensive list of all users stored within your database. Use an asynchronous function in Next.js with Prisma to:

  • Create and retrieve user information (e.g., name, email).
  • Create and fetch related posts for each user.
  • Fetch profile data for each user.
  • Retrieve all user records along with their associated posts and profile information.
1async function FetchUsers() {
2  await prisma.user.create({
3    data: {
4      name: 'Alice',
5      email: 'alice@prisma.io',
6      posts: {
7        create: { title: 'Hello World' },
8      },
9      profile: {
10        create: { bio: 'I like turtles' },
11      },
12    },
13  })
14
15  const allUsers = await prisma.user.findMany({
16    include: {
17      posts: true,
18      profile: true,
19    },
20  })
21  console.log(allUsers)
22}

Creating a Custom Login Page in Auth.js

To create a custom login page:

1// app/login/page.tsx
2import { signIn } from 'next-auth/react';
3
4export default function LoginPage() {
5  return (
6    <div>
7      <h1>Login</h1>
8      <button onClick={() => signIn('github')}>Sign in with GitHub</button>
9      <button onClick={() => signIn('google')}>Sign in with Google</button>
10    </div>
11  );
12}

Auth.js Callbacks

Define custom callbacks in auth.ts:

1callbacks: {
2  async signIn({ user, account, profile, email, credentials }) {
3    // Add custom logic here
4    return true;
5  },
6  async session({ session, token }) {
7    session.user.id = token.id;
8    return session;
9  },
10  async jwt({ token, user }) {
11    if (user) {
12      token.id = user.id;
13    }
14    return token;
15}

Creating Protected Routes with Auth.js

Tip: If you are un Next.js Pages Router you can use getServerSideProps with this method To protect routes, use the getServerSideProps function with getSession:

1import { getSession } from 'next-auth/react';
2
3export async function getServerSideProps(context) {
4  const session = await getSession(context);
5
6  if (!session) {
7    return {
8      redirect: {
9        destination: '/login',
10        permanent: false,
11      },
12    };
13  }
14
15  return {
16    props: { session },
17  };
18}

๐Ÿ’Ž Client-Side Component for Authentication

To authenticate users on the client-side in Next.js, employ thenext-authpackage. The SessionProvider component enables seamless access to the user's session. Here's a code example showcasing this setup:

1'use client';
2
3import { useSession, SessionProvider } from "next-auth/react";
4
5const ClientComponent = () => {
6const session = useSession();
7
8return (
9  <SessionProvider>
10    <p>Welcome {session?.user?.name}</p>
11  </SessionProvider>
12  )
13}

๐Ÿฆบ Server-Side Component for Authentication

To authenticate users on the server side in Next.js, employ the auth method to retrieve their session information. For server-side rendering using React Server Components, here's how you can achieve this:

1import { auth } from 'auth'
2
3export default async function Page() {
4  const session = await auth()
5  return (
6    <div className='space-y-2'>
7      <h1 className='text-3xl font-bold'>React Server Component</h1>
8      <p>Welcome {session?.user.name}!</p>
9    </div>
10  )
11}

Conclusion

Integrating Auth.js and Prisma in a Next.js project with the App Router enables secure authentication on both the client-side and server-side. Following the provided steps, you can establish authentication using external providers like GitHub and Google, design custom login pages, safeguard specific routes, and effectively manage user sessions.

๐Ÿ“ข Resource :

Join our newsletter for the
latest update

By subscribing you agree to receive the Paddle newsletter. Unsubscribe at any time.