diff --git a/docs/quick-start/nextjs-app-router.mdx b/docs/quick-start/nextjs-app-router.mdx
index 7e6a4106..c6296b84 100644
--- a/docs/quick-start/nextjs-app-router.mdx
+++ b/docs/quick-start/nextjs-app-router.mdx
@@ -101,6 +101,11 @@ npx zenstack generate && npx prisma db push
### 4. Configure NextAuth to use credential-based auth
+First, we need to install type definitions for bcryptjs
+```bash
+npm install --save @types/bcryptjs
+```
+
Now let's update `/src/server/auth.ts` to the content below to
use credentials auth and JWT-based session:
diff --git a/docs/quick-start/nextjs.mdx b/docs/quick-start/nextjs.mdx
index 53a77857..11267bd1 100644
--- a/docs/quick-start/nextjs.mdx
+++ b/docs/quick-start/nextjs.mdx
@@ -101,17 +101,27 @@ npx zenstack generate && npx prisma db push
### 4. Configure NextAuth to use credential-based auth
+First, we need to install type definitions for bcryptjs
+```bash
+npm install --save @types/bcryptjs
+```
+
Now let's update `/src/server/auth.ts` to the content below to
use credentials auth and JWT-based session:
```ts title='/src/server/auth.ts'
-import { PrismaAdapter } from '@next-auth/prisma-adapter';
-import type { PrismaClient } from '@prisma/client';
-import { compare } from 'bcryptjs';
-import type { GetServerSidePropsContext } from 'next';
-import NextAuth, { getServerSession, type DefaultSession, type NextAuthOptions } from 'next-auth';
-import CredentialsProvider from 'next-auth/providers/credentials';
-import { db } from './db';
+import { PrismaAdapter } from "@auth/prisma-adapter";
+import type { PrismaClient } from "@prisma/client";
+import { compare } from "bcryptjs";
+import type { GetServerSidePropsContext } from "next";
+import NextAuth, {
+ getServerSession,
+ type DefaultSession,
+ type NextAuthOptions,
+} from "next-auth";
+import CredentialsProvider from "next-auth/providers/credentials";
+import { db } from "./db";
+import { Adapter } from "next-auth/adapters";
/**
* Module augmentation for `next-auth` types.
* Allows us to add custom properties to the `session` object and keep type
@@ -119,12 +129,12 @@ import { db } from './db';
*
* @see https://next-auth.js.org/getting-started/typescript#module-augmentation
**/
-declare module 'next-auth' {
- interface Session extends DefaultSession {
- user: {
- id: string;
- } & DefaultSession['user'];
- }
+declare module "next-auth" {
+ interface Session extends DefaultSession {
+ user: {
+ id: string;
+ } & DefaultSession["user"];
+ }
}
/**
@@ -134,46 +144,50 @@ declare module 'next-auth' {
* @see https://next-auth.js.org/configuration/options
**/
export const authOptions: NextAuthOptions = {
- session: {
- strategy: 'jwt',
- },
- // Include user.id on session
- callbacks: {
- session({ session, token }) {
- if (session.user) {
- session.user.id = token.sub!;
- }
- return session;
- },
+ session: {
+ strategy: "jwt",
+ },
+ // Include user.id on session
+ callbacks: {
+ session({ session, token }) {
+ if (session.user) {
+ session.user.id = token.sub!;
+ }
+ return session;
},
- // Configure one or more authentication providers
- adapter: PrismaAdapter(db),
- providers: [
- CredentialsProvider({
- credentials: {
- email: { type: 'email' },
- password: { type: 'password' },
- },
- authorize: authorize(db),
- }),
- ],
+ },
+ // Configure one or more authentication providers
+ adapter: PrismaAdapter(db) as Adapter,
+ providers: [
+ CredentialsProvider({
+ credentials: {
+ email: { type: "email" },
+ password: { type: "password" },
+ },
+ authorize: authorize(db),
+ }),
+ ],
};
function authorize(prisma: PrismaClient) {
- return async (credentials: Record<'email' | 'password', string> | undefined) => {
- if (!credentials) throw new Error('Missing credentials');
- if (!credentials.email) throw new Error('"email" is required in credentials');
- if (!credentials.password) throw new Error('"password" is required in credentials');
- const maybeUser = await prisma.user.findFirst({
- where: { email: credentials.email },
- select: { id: true, email: true, password: true },
- });
- if (!maybeUser || !maybeUser.password) return null;
- // verify the input password with stored hash
- const isValid = await compare(credentials.password, maybeUser.password);
- if (!isValid) return null;
- return { id: maybeUser.id, email: maybeUser.email };
- };
+ return async (
+ credentials: Record<"email" | "password", string> | undefined,
+ ) => {
+ if (!credentials) throw new Error("Missing credentials");
+ if (!credentials.email)
+ throw new Error('"email" is required in credentials');
+ if (!credentials.password)
+ throw new Error('"password" is required in credentials');
+ const maybeUser = await prisma.user.findFirst({
+ where: { email: credentials.email },
+ select: { id: true, email: true, password: true },
+ });
+ if (!maybeUser?.password) return null;
+ // verify the input password with stored hash
+ const isValid = await compare(credentials.password, maybeUser.password);
+ if (!isValid) return null;
+ return { id: maybeUser.id, email: maybeUser.email };
+ };
}
/**
@@ -183,10 +197,10 @@ function authorize(prisma: PrismaClient) {
* @see https://next-auth.js.org/configuration/nextjs
**/
export const getServerAuthSession = (ctx: {
- req: GetServerSidePropsContext['req'];
- res: GetServerSidePropsContext['res'];
+ req: GetServerSidePropsContext["req"];
+ res: GetServerSidePropsContext["res"];
}) => {
- return getServerSession(ctx.req, ctx.res, authOptions);
+ return getServerSession(ctx.req, ctx.res, authOptions);
};
export default NextAuth(authOptions);
@@ -204,7 +218,7 @@ value (use a complex secret in production and don't check it into git):
ZenStack has built-in support for Next.js and can provide database CRUD services
automagically, so you don't need to write it yourself.
-First install the `@zenstackhq/server` and `@zenstackhq/swr` packages:
+First, install the `@zenstackhq/server` and `@zenstackhq/swr` packages:
```bash
npm install @zenstackhq/server swr
@@ -215,17 +229,17 @@ Let's mount it to the `/api/model/[...path]` endpoint. Create a `/src/pages/api/
file and fill in the content below:
```ts title='/src/pages/api/model/[...path].ts'
-import { NextRequestHandler } from '@zenstackhq/server/next';
-import { enhance } from '@zenstackhq/runtime';
-import type { NextApiRequest, NextApiResponse } from 'next';
-import { getServerAuthSession } from '../../../server/auth';
-import { prisma } from '../../../server/db';
+import { NextRequestHandler } from "@zenstackhq/server/next";
+import { enhance } from "@zenstackhq/runtime";
+import type { NextApiRequest, NextApiResponse } from "next";
+import { getServerAuthSession } from "../../../server/auth";
+import { db } from "../../../server/db";
async function getPrisma(req: NextApiRequest, res: NextApiResponse) {
- const session = await getServerAuthSession({ req, res });
- // create a wrapper of Prisma client that enforces access policy,
- // data validation, and @password, @omit behaviors
- return enhance(prisma, { user: session?.user });
+ const session = await getServerAuthSession({ req, res });
+ // create a wrapper of Prisma client that enforces access policy,
+ // data validation, and @password, @omit behaviors
+ return enhance(db, { user: session?.user });
}
export default NextRequestHandler({ getPrisma });
@@ -254,78 +268,105 @@ Now we're ready to implement the signup/signin flow.
### 6. Implement Signup/Signin
+First, NextAuth and React Query require context providers to be set up. Let's add it to `/src/pages/_app.tsx`:
+
+```tsx title='/src/pages/_app.tsx'
+import { type AppType } from "next/app";
+import { type Session } from "next-auth";
+import { SessionProvider } from "next-auth/react";
+
+import "../styles/globals.css";
+
+const MyApp: AppType<{ session: Session | null }> = ({
+ Component,
+ pageProps: { session, ...pageProps },
+}) => {
+ return (
+
- {post.title} - by {post.author.email} -
-+ {post.title} + by {post.author.email} +
+Loading ...
; - - return ( -Loading ...
; + + return ( +