Implementing Client and Server-Side Authentication with Auth.js

npmixnpmix
89
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:

typescript
npx shadcn@latest init
npx 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:

typescript
npm 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.:

typescript
npx 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.

typescript
//For Google:
https://{YOUR_DOMAIN}/api/auth/callback/google

//For Github:
https://{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:

typescript
// ./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.

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

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

typescript
// app/api/auth/[...nextauth]/route.ts

import { handlers } from '@/auth'
export const { GET, POST } = handlers

Environment Variables in Auth.js

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

typescript
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: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.

typescript
// 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);

Install and Initialize Prisma

Step 1: Install Prisma

To get started, install Prisma:

typescript
npm install @prisma/client @auth/prisma-adapter
npm 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.

typescript
DATABASE_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.

typescript
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 = prisma

Update your ./auth.ts

typescript
import 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: [...],
})

Step 2: Initialize Prisma

Initialize Prisma with the following command:

typescript
npx 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:

typescript
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])
}

Step 4: Run Prisma Migrate

Apply the schema with:

typescript
npx 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

typescript
npx prisma generate

Step 5: Use Prisma Studio

To view and edit data, use Prisma Studio:

typescript
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:

  • 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.
typescript
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)
}

Creating a Custom Login Page in Auth.js

To create a custom login page:

typescript
// 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>
  );
}

Auth.js Callbacks

Define custom callbacks in auth.ts:

typescript
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;
}

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:

typescript
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 },
  };
}

💎 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:

typescript
'use client';

import { useSession, SessionProvider } from "next-auth/react";

const ClientComponent = () => {
const session = useSession();

return (
  <SessionProvider>
    <p>Welcome {session?.user?.name}</p>
  </SessionProvider>
  )
}

🦺 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:

typescript
import { auth } from 'auth'

export default async function Page() {
  const session = await auth()
  return (
    <div className='space-y-2'>
      <h1 className='text-3xl font-bold'>React Server Component</h1>
      <p>Welcome {session?.user.name}!</p>
    </div>
  )
}

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 :

Similar articles

Never miss an update

Subscribe to receive news and special offers.

By subscribing you agree to our Privacy Policy.