Implementing Client and Server-Side Authentication with Auth.js


Never miss an update
Subscribe to receive news and special offers.
By subscribing you agree to our Privacy Policy.
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.
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.
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.
To successfully use Auth.js with Prisma in a Next.js application, you'll need:
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:
npx shadcn@latest init
npx shadcn@latest add buttonFor more information on how to install shadcn, see How to Install shadcn in a Next.js Project: Step-by-Step Guide
To install Auth.js, use the following command:
npm install next-auth@betaThe 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.:
npx auth secretDepending 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.
//For Google:
https://{YOUR_DOMAIN}/api/auth/callback/google
//For Github:
https://{YOUR_DOMAIN}/api/auth/callback/githubIn development mode, use localhost:3000for the domain.
To set up callback URL processing:
Create a new auth.ts at at the root of your project with this code:
// ./auth.ts
import NextAuth from 'next-auth';
const authOptions = {
providers: [...],
secret: process.env.AUTH_SECRET,
};
export const { handlers, auth, signIn, signOut } = NextAuth(authOptions);Add middleware to keep the session alive and update its expiration.
export { auth as middleware } from '@/auth'Add a route handler under /app/api/auth/[...nextauth]/route.ts:
// app/api/auth/[...nextauth]/route.ts
import { handlers } from '@/auth'
export const { GET, POST } = handlersAdd the necessary environment variables in a .env file at the root of your project:
AUTH_GITHUB_ID=your-github-auth-id
AUTH_GITHUB_SECRET=your-github-key-secret
AUTH_GOOGLE_ID=your-google-auth-id
AUTH_GOOGLE_SECRET=your-google-key-secret
AUTH_SECRET=your-auth-secret
NEXTAUTH_URL=http://localhost:3000To 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.
// auth.ts
import { AuthOptions } from 'next-auth';
import Google from 'next-auth/providers/google';
import Github from 'next-auth/providers/github';
const authOptions: AuthOptions = {
providers: [
Google({
authorization: {
params: {
prompt: 'consent',
access_type: 'offline',
response_type: 'code',
},
},
}),
Github,
],
callbacks: {
async signIn({ user, account, profile, email, credentials }) {
return true;
},
},
};
export const { handlers, auth, signIn, signOut } = NextAuth(authOptions);To get started, install Prisma:
npm install @prisma/client @auth/prisma-adapter
npm install prisma --save-devTo 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.
DATABASE_URL=postgresql://USER:PASSWORD@HOST:PORT/DATABASE?schema=SCHEMATo 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.
import { PrismaClient } from '@prisma/client'
const globalForPrisma = globalThis as unknown as { prisma: PrismaClient }
export const prisma = globalForPrisma.prisma
? globalForPrisma.prisma
: new PrismaClient();
if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prismaimport NextAuth from 'next-auth';
import { PrismaAdapter } from '@auth/prisma-adapter'
import { prisma } from '@/prisma'
export const { handlers, auth, signIn, signOut } = NextAuth({
adapter: PrismaAdapter(prisma),
providers: [...],
})Initialize Prisma with the following command:
npx prisma initTo 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:
datasource db {
provider = 'postgresql'
url = env('DATABASE_URL')
}
generator client {
provider = 'prisma-client-js'
}
model User {
id String @id @default(cuid())
name String?
email String @unique
emailVerified DateTime?
image String?
accounts Account[]
sessions Session[]
// Optional for WebAuthn support
Authenticator Authenticator[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model Account {
userId String
type String
provider String
providerAccountId String
refresh_token String?
access_token String?
expires_at Int?
token_type String?
scope String?
id_token String?
session_state String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@id([provider, providerAccountId])
}
model Session {
sessionToken String @unique
userId String
expires DateTime
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model VerificationToken {
identifier String
token String
expires DateTime
@@id([identifier, token])
}
// Optional for WebAuthn support
model Authenticator {
credentialID String @unique
userId String
providerAccountId String
credentialPublicKey String
counter Int
credentialDeviceType String
credentialBackedUp Boolean
transports String?
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@id([userId, credentialID])
}Apply the schema with:
npx prisma migrate devThis 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
.envfile at the root of your project. Note Prisma doesn’t support.env.localsyntax, it must be named.env.
You can use this command to generate the prisma client
npx prisma generateTo view and edit data, use Prisma Studio:
npx 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:
async function FetchUsers() {
await prisma.user.create({
data: {
name: 'Alice',
email: 'alice@prisma.io',
posts: {
create: { title: 'Hello World' },
},
profile: {
create: { bio: 'I like turtles' },
},
},
})
const allUsers = await prisma.user.findMany({
include: {
posts: true,
profile: true,
},
})
console.log(allUsers)
}To create a custom login page:
// app/login/page.tsx
import { signIn } from 'next-auth/react';
export default function LoginPage() {
return (
<div>
<h1>Login</h1>
<button onClick={() => signIn('github')}>Sign in with GitHub</button>
<button onClick={() => signIn('google')}>Sign in with Google</button>
</div>
);
}Define custom callbacks in auth.ts:
callbacks: {
async signIn({ user, account, profile, email, credentials }) {
// Add custom logic here
return true;
},
async session({ session, token }) {
session.user.id = token.id;
return session;
},
async jwt({ token, user }) {
if (user) {
token.id = user.id;
}
return token;
}Tip: If you are un Next.js Pages Router you can use getServerSideProps with this method To protect routes, use the
getServerSidePropsfunction withgetSession:
import { getSession } from 'next-auth/react';
export async function getServerSideProps(context) {
const session = await getSession(context);
if (!session) {
return {
redirect: {
destination: '/login',
permanent: false,
},
};
}
return {
props: { session },
};
}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:
'use client';
import { useSession, SessionProvider } from "next-auth/react";
const ClientComponent = () => {
const session = useSession();
return (
<SessionProvider>
<p>Welcome {session?.user?.name}</p>
</SessionProvider>
)
}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:
import { auth } from 'auth'
export default async function Page() {
const session = await getSession()
return (
<div className='space-y-2'>
<h1 className='text-3xl font-bold'>React Server Component</h1>
<p>Welcome {session?.user.name}!</p>
</div>
)
}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.