diff --git a/cSpell.json b/cSpell.json index 44b46e1a7e..93bd268c8d 100644 --- a/cSpell.json +++ b/cSpell.json @@ -4,6 +4,7 @@ "words": [ "Amplication", "Astro", + "Authjs", "autocompletions", "Autoincrementing", "autoscale", diff --git a/content/800-guides/350-authjs-nextjs.mdx b/content/800-guides/350-authjs-nextjs.mdx new file mode 100644 index 0000000000..8ec7c3d9e9 --- /dev/null +++ b/content/800-guides/350-authjs-nextjs.mdx @@ -0,0 +1,631 @@ +--- +title: 'How to use Prisma ORM with Auth.js and Next.js' +metaTitle: 'How to use Prisma ORM and Prisma Postgres with Auth.js and Next.js' +description: 'Learn how to use Prisma ORM in a Next.js app with Auth.js' +sidebar_label: 'Auth.js (with Next.js)' +image: '/img/guides/prisma-authjs-nextjs-cover.png' +completion_time: '25 min' +community_section: true +--- + +## Introduction + +[Auth.js](https://authjs.dev/) is a flexible, open-source authentication library designed to simplify adding authentication to your Next.js applications. + +In this guide, you'll wire Auth.js into a brand-new [Next.js](https://nextjs.org/) app and persist users in a [Prisma Postgres](https://prisma.io/postgres) database. You can find a complete example of this guide on [GitHub](https://github.com/prisma/prisma-examples/tree/latest/orm/authjs-nextjs). + +## Prerequisites + +- [Node.js 18+](https://nodejs.org) +- Basic familiarity with Next.js App Router and Prisma + +## 1. Set up your project + +Create a new Next.js application: + +```terminal +npx create-next-app@latest authjs-prisma +``` + +It will prompt you to customize your setup. Choose the defaults: + +:::info + +- *Would you like to use TypeScript?* `Yes` +- *Would you like to use ESLint?* `Yes` +- *Would you like to use Tailwind CSS?* `Yes` +- *Would you like your code inside a `src/` directory?* `No` +- *Would you like to use App Router?* (recommended) `Yes` +- *Would you like to use Turbopack for `next dev`?* `Yes` +- *Would you like to customize the import alias (`@/*` by default)?* `No` + +::: + +Navigate to the project directory: + +```terminal +cd authjs-prisma +``` + +## 2. Install and configure Prisma + +### 2.1. Install dependencies + +To get started with Prisma, you'll need to install a few dependencies: + + + +```terminal +npm install prisma tsx --save-dev +npm install @prisma/extension-accelerate @prisma/client +``` + + +```terminal +npm install prisma tsx --save-dev +npm install @prisma/client +``` + + + +Once installed, initialize Prisma in your project: + +```terminal +npx prisma init --db --output ../app/generated/prisma +``` + +:::info +You'll need to answer a few questions while setting up your Prisma Postgres database. Select the region closest to your location and a memorable name for your database like "My Auth.js Project" +::: + +This will create: + +- A `prisma` directory with a `schema.prisma` file. +- A Prisma Postgres database. +- A `.env` file containing the `DATABASE_URL` at the project root. +- A schema configuration that specifies where the Prisma Client will be generated (`../app/generated/prisma`). + +### 2.2. Define your Prisma Schema + +In the `prisma/schema.prisma` file, swap the provider to `prisma-client` and add the runtime `edge-light` to the generator: + +```prisma file=prisma/schema.prisma +generator client { + //edit-next-line + provider = "prisma-client" + output = "../app/generated/prisma" + //add-next-line + runtime = "edge-light" +} + +datasource db { + provider = "postgresql" + url = env("DATABASE_URL") +} +``` + +Add the following models to the `schema.prisma` file, these models are provided by Auth.js: + +```prisma file=prisma/schema.prisma +//add-start +model Account { + id String @id @default(cuid()) + userId String @map("user_id") + type String + provider String + providerAccountId String @map("provider_account_id") + refresh_token String? @db.Text + access_token String? @db.Text + expires_at Int? + token_type String? + scope String? + id_token String? @db.Text + session_state String? + + user User @relation(fields: [userId], references: [id], onDelete: Cascade) + + @@unique([provider, providerAccountId]) + @@map("accounts") +} + +model Session { + id String @id @default(cuid()) + sessionToken String @unique @map("session_token") + userId String @map("user_id") + expires DateTime + user User @relation(fields: [userId], references: [id], onDelete: Cascade) + + @@map("sessions") +} + +model User { + id String @id @default(cuid()) + name String? + email String? @unique + emailVerified DateTime? @map("email_verified") + image String? + accounts Account[] + sessions Session[] + + @@map("users") +} + +model VerificationToken { + identifier String + token String + expires DateTime + + @@unique([identifier, token]) + @@map("verification_tokens") +} +//add-end +``` + +This creates the following models: + +- **`Account`**: Stores OAuth provider information (access tokens, refresh tokens, provider account IDs) and enables users to sign in with multiple providers while maintaining a single user record. + +- **`Session`**: Tracks authenticated user sessions with a unique session token, user ID, and expiration time to maintain authentication state across requests. + +- **`User`**: The core model storing user information (name, email, profile image). Users can have multiple accounts from different providers and multiple active sessions. + +- **`VerificationToken`**: Stores temporary tokens for email verification, password reset, and other security operations with expiration times. + +### 2.3. Configure the Prisma Client generator + +Now, run the following command to create the database tables and generate the Prisma Client: + +```terminal +npx prisma migrate dev --name init +``` + +### 2.4 Create a Prisma Client + +Create a new folder in the root called `lib` and create a new file called `prisma.ts` in it. This file will contain the Prisma Client: + + + +```typescript file=lib/prisma.ts showLineNumbers +//add-start +import { PrismaClient } from '../app/generated/prisma' +import { withAccelerate } from '@prisma/extension-accelerate' + +const globalForPrisma = global as unknown as { + prisma: PrismaClient +} + +const prisma = globalForPrisma.prisma || new PrismaClient().$extends(withAccelerate()) + +if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma + +export default prisma +//add-end +``` + + + +```typescript file=lib/prisma.ts showLineNumbers +//add-start +import { PrismaClient } from '../app/generated/prisma' + +const globalForPrisma = global as unknown as { + prisma: PrismaClient +} + +const prisma = globalForPrisma.prisma || new PrismaClient() + +if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma + +export default prisma +//add-end +``` + + + +## 3. Set up Auth.js credentials + +### 3.1. Install dependencies + +Install the Auth.js dependencies: + +```terminal +npm install @auth/prisma-adapter next-auth@beta +``` + +### 3.2 Credentials + +For this guide, you'll be setting up OAuth with Github. For this, you'll need 3 environment variables: + +- `AUTH_SECRET` - Provided by Auth.js +- `CLIENT_ID` - Provided by Github +- `CLIENT_SECRET` - Provided by Github + +To get the `AUTH_SECRET`, you can run the following command: + +```terminal +npx auth secret --copy +``` + +- `--copy` will copy the secret to your clipboard. _(Normally, just running `npx auth secret` will add the secret to your `.env.local` file. To keep it tidy, you can use `--copy` and add it to the `.env` file that Prisma created earlier.)_ + +Add the following to the `.env` file: + +```env file=.env +DATABASE_URL= +//add-start +AUTH_SECRET= +//add-end +``` + +To get the `CLIENT_ID` and `CLIENT_SECRET`, you can create a new OAuth application on Github. + +1. Navigate to [Github Developer Settings](https://github.com/settings/developers) +2. Click on `New OAuth App` +3. Enter a name for your app, a home page URL, and a callback URL +- Name: `Auth.js + Prisma` (Or anything you want) +- Homepage URL: `http://localhost:3000` +- Callback URL: `http://localhost:3000/api/auth/callback/github` +4. Click `Register application` +5. Click `Generate new client secret` and copy the `Client ID` and `Client Secret`. +6. Add the `Client ID` and `Client Secret` to the `.env` file: + +```env file=.env +DATABASE_URL= +AUTH_SECRET= +//add-start +AUTH_GITHUB_ID= +AUTH_GITHUB_SECRET= +//add-end +``` + +### 3.3. Configure Auth.js + +In the `/lib` folder, create a new file called `auth.ts` and add the following code: + +```typescript file=lib/auth.ts +import NextAuth from 'next-auth' + +export const { handlers, auth, signIn, signOut } = NextAuth({ + providers: [], +}) +``` + +Next, you'll need to add the Github provider to the `auth.ts` file: + +```typescript file=lib/auth.ts +import NextAuth from 'next-auth' +//add-next-line +import GitHub from 'next-auth/providers/github' + +export const { handlers, auth, signIn, signOut } = NextAuth({ + //edit-next-line + providers: [GitHub], +}) +``` + +Users will now be able to sign in with Github. To add them to your database, you'll need to use the [Prisma Adapter](https://authjs.dev/getting-started/adapters/prisma): + +```typescript file=lib/auth.ts +import NextAuth from 'next-auth' +//add-start +import { PrismaAdapter } from '@auth/prisma-adapter' +import prisma from '@/lib/prisma' +//add-end +import GitHub from 'next-auth/providers/github' + +export const { handlers, auth, signIn, signOut } = NextAuth({ + //edit-next-line + adapter: PrismaAdapter(prisma), + providers: [GitHub], +}) +``` + +In the root, create a new file called `middleware.ts`. This will protect your routes and ensure that only authenticated users can access them: + +```tsx file=middleware.ts +export { auth as middleware } from '@/lib/auth' +``` + +### 3.4. Configure the Route + +The route handler is required to handle authentication requests from Auth.js. It exports the `GET` and `POST` handlers that Auth.js uses for sign-in, sign-out, and callback operations. + +Create a new file at `app/api/auth/[...nextauth]/route.ts`: + +```bash +mkdir -p app/api/auth/[...nextauth] +touch app/api/auth/[...nextauth]/route.ts +``` + +Add the following code to the file: + +```tsx file=app/api/auth/[...nextauth]/route.ts +import { handlers } from '@/lib/auth' + +export const { GET, POST } = handlers +``` + +That's it! Your app is now secured. To see more configuration options, check out the [Auth.js Middleware documentation](https://next-auth.js.org/configuration/nextjs#middleware). + +## 4. Auth components + +You will be creating a Sign In and Sign Out button. Create a `/components` folder in the root and add a new file called `auth-components.tsx` in it. + +Start by importing the `signIn` and `signOut` functions from the `auth` file: + +```tsx file=components/auth-components.tsx +//add-next-line +import { signIn, signOut } from "@/lib/auth" +``` + +Next, create the `SignIn` and `SignOut` components: + +```tsx file=components/auth-components.tsx +import { signIn, signOut } from "@/lib/auth" + +//add-start +export function SignIn({ provider }: { provider?: string }) { + return ( +
+ +
+ ) +} + +export function SignOut() { + return ( +
+ +
+ ) +} +//add-end +``` + +To add functionality to both of the buttons, add an action to the form that calls the `signIn` and `signOut` functions respectively: + +```tsx file=components/auth-components.tsx +import { signIn, signOut } from "@/lib/auth" + +export function SignIn({ provider }: { provider?: string }) { + return ( +
{ + "use server" + await signIn(provider) + }} + //add-end + > + +
+ ) +} + +export function SignOut() { + return ( +
{ + "use server" + await signOut() + }} + //add-end + className="w-full" + > + +
+ ) +} +``` + +## 5. Add the components to your app + +### 5.1. Set up the basic page structure + +In the `/app` folder, replace the `page.tsx` file with the following code: + +```tsx file=app/page.tsx +//add-start +const Page = async () => { + return ( +
+
+

Auth.js + Prisma

+
+
+ ); +}; + +export default Page; +//add-end +``` + +### 5.2. Add imports and authentication check + +Import the required components and add session checking: + +```tsx file=app/page.tsx +//add-start +import { SignIn, SignOut } from "@/components/auth-components"; +import { auth } from "@/lib/auth"; +//add-end + +const Page = async () => { + //add-next-line + const session = await auth(); + + return ( +
+
+

Auth.js + Prisma

+
+
+ ); +}; + +export default Page; +``` + +### 5.3. Show content based on auth state + +Add the logic to show different content based on whether the user is signed in: + + +```tsx file=app/page.tsx +import { SignIn, SignOut } from "@/components/auth-components"; +import { auth } from "@/lib/auth"; + +const Page = async () => { + const session = await auth(); + + return ( +
+
+

Auth.js + Prisma

+ + {!session ? ( +
+ +
+ ) : ( +
+
+

Signed in as:

+

{session.user?.email}

+
+ +
+

Data fetched from DB with Prisma:

+
+ +
+ +
+
+ )} +
+
+ ); +}; + +export default Page; +``` + +### 5.4. Add the user data to the page + +If the user is signed in, you can fetch the user data from the database and display it on the page. + +```tsx file=app/page.tsx +import { SignIn, SignOut } from "@/components/auth-components"; +import { auth } from "@/lib/auth"; +//add-next-line +import prisma from "@/lib/prisma"; + +const Page = async () => { + const session = await auth(); + //add-start + let user = null; + + if (session) { + user = await prisma.user.findUnique({ + where: { + id: session.user?.id, + } + }); + } + //add-end + + return ( +
+
+

Auth.js + Prisma

+ + {!session ? ( +
+ +
+ ) : ( +
+
+

Signed in as:

+

{session.user?.email}

+
+ +
+

Data fetched from DB with Prisma:

+
+ + //add-start +
+
+                {JSON.stringify(user, null, 2)}
+              
+
+ //add-end + +
+ +
+
+ )} +
+
+ ); +}; + +export default Page; +``` + +## 6. Test it out + +:::warning + +Before starting the development server, note that if you are using Next.js v15.2.0 or v15.2.1, do not use Turbopack as there is a known [issue](https://github.com/vercel/next.js/issues/76497). Remove Turbopack from your dev script by updating your `package.json` +```json file=package.json +"script":{ + //delete-start + "dev": "next dev --turbopack", + //delete-end + //add-start + "dev": "next dev", + //add-end +} +``` +This change is not needed on any versions before or after. + +::: + +Your application is now fully configured. + +1. Start the development server to test it: + +```terminal +npm run dev +``` + +2. Navigate to `http://localhost:3000` in your browser. You should see the home page with a "Sign In with github" button. + +3. Click on **Sign In with github**, authorize the app, and you should be redirected to the dashboard. You can then sign out and sign back in. + +4. To view the user data directly in your database, you can use Prisma Studio: + +```terminal +npx prisma studio +``` + +5. This will open a new tab in your browser where you can see the `User`, `Session`, and `Account` tables and their contents. + +:::success + +Congratulations! You now have a fully functional authentication system built with Auth.js, Prisma, and Next.js. + +::: \ No newline at end of file diff --git a/sidebars.ts b/sidebars.ts index 84ccb81fa3..e0b6b57ba4 100644 --- a/sidebars.ts +++ b/sidebars.ts @@ -446,6 +446,7 @@ const sidebars: SidebarsConfig = { "guides/shopify", "guides/permit-io-access-control", "guides/betterauth-nextjs", + "guides/authjs-nextjs", ].sort(), }, { diff --git a/static/img/guides/prisma-authjs-nextjs-cover.png b/static/img/guides/prisma-authjs-nextjs-cover.png new file mode 100644 index 0000000000..40cbdee455 Binary files /dev/null and b/static/img/guides/prisma-authjs-nextjs-cover.png differ