Implementing Client and Server-Side Authentication with Auth.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:3000
for the domain.
To set up callback URL processing:
- For Google, refer to Google OAuth Configuration or Google OAuth documentation.
- For GitHub, visit GitHub: Creating an OAuth App and Configure your GitHub OAuth Apps.
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.ts
and 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 withgetSession
:
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-auth
package. 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.