将 NextAuth (Auth.js) 与 MongoDB (mongoose) 和中间件结合使用

问题描述 投票:0回答:1

简介

您好,在本文中,我们想探索如何将 NextAuth 与 MongoDB 和中间件结合使用。

“注意:如果你想在 Mongoose 中使用中间件,这是不可能的。”

Next.js 边缘运行时

Mongoose 目前不支持 Next.js Edge Runtime。虽然您可以在 Edge Runtime 中导入 Mongoose,但您将获得 Mongoose 的浏览器库。 Mongoose 无法在 Edge Runtime 中连接到 MongoDB,因为 Edge Runtime 目前不支持 Node.js net API,而 MongoDB Node Driver 使用 Node.js net API 连接到 MongoDB。

https://mongoosejs.com/docs/nextjs.html

套餐

  1. npx create-next-app@latest app
  2. npm install zod
  3. npm install mongoose
  4. npm install next-auth
  5. npm install bcryptjs @types/bcryptjs

步骤

  1. 开发登录和注销页面并实现带有表单验证的前端表单

/app/signin/page.tsx

import SignInForm from "@/components/SignInForm";

import { Metadata } from "next";

export async function generateMetadata(): Promise<Metadata> {
  return {
    title: "Sign In",
  };
}

async function SignIn() {
  return (
    <div className="gird grid-cols-1 justify-items-center content-center h-full">
      <SignInForm />
    </div>
  );
}

export default SignIn;

/components/SignInForm.tsx

"use client";

import { z } from "zod";

import { useEffect, useState } from "react";

import { signIn } from "next-auth/react";

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

import { useRouter, useSearchParams } from "next/navigation";

const userSchema = z.object({
  email: z.string().email("Invalid Email."),
  password: z.string().min(5, "Password Must Be Least At 5 Character long."),
});

type User = z.infer<typeof userSchema>;

function SignInForm() {
  const [err, setErr] = useState<{
    email: string | undefined;
    password: string | undefined;
  }>({ email: undefined, password: undefined });

  const { data: session } = useSession();

  const router = useRouter();

  const redirect = useSearchParams().get("redirect");

  async function submitHandler(formData: FormData) {
    const email = formData.get("email") as string | undefined;
    const password = formData.get("password") as string | undefined;

    if (!email || !password)
      return setErr({
        email: email ? undefined : "Please Enter Your Email.",
        password: password ? undefined : "Please Enter Your Password.",
      });

    const user: User = { email, password };

    const result = userSchema.safeParse(user);

    if (result.error) {
      const zodErrors = result.error.errors;

      const email = zodErrors.find(
        (item) => item.path.join("") === "email"
      )?.message;
      const password = zodErrors.find(
        (item) => item.path.join("") === "password"
      )?.message;

      const errors = { email, password };

      setErr(errors);

      return;
    }

    setErr({ email: undefined, password: undefined });

    try {
      const res = await signIn("credentials", {
        redirect: false,
        email,
        password,
      });

      if (res?.error) console.log("Faild To Sign In.");
    } catch (err) {
      console.log(err);
    }
  }

  useEffect(() => {
    if (session?.user) router.push("/signout");
  }, [session, router, redirect]);

  return (
    <form
      action={submitHandler}
      className="bg-slate-100 text-gray-700 p-4 rounded-md"
    >
      <h2 className="mb-2">Sign IN</h2>
      <div>
        <label htmlFor="email">Email:</label>
        <br />
        <input
          type="email"
          id="email"
          name="email"
          className="px-2 py-1 my-1 bg-transparent border border-gray-600 rounded-md"
          placeholder="Enter Your Email"
          autoFocus
        />
        {err.email && <small className="block text-red-500">{err.email}</small>}
      </div>
      <div>
        <label htmlFor="password">Password:</label>
        <br />
        <input
          type="password"
          id="password"
          name="password"
          className="px-2 py-1 my-1 bg-transparent border border-gray-600 rounded-md"
          placeholder="Enter Your Password"
          autoFocus
        />
        {err.password && (
          <small className="block text-red-500">{err.password}</small>
        )}
      </div>
      <button
        type="submit"
        className="block mx-auto px-2 py-1 mt-4 bg-gray-600 text-slate-100 hover:bg-slate-700 transition-all rounded-md"
      >
        Submit
      </button>
    </form>
  );
}

export default SignInForm;

/app/signout/page.tsx

import SignOutBTN from "@/components/SignOutBTN";

import { Metadata } from "next";

export async function generateMetadata(): Promise<Metadata> {
  return {
    title: "Sign Out",
  };
}

function SignOut() {
  return (
    <div className="gird grid-cols-1 justify-items-center content-center h-full">
      <SignOutBTN />
    </div>
  );
}

export default SignOut;

/components/SignOutBTN.tsx

"use client";

import { signOut } from "next-auth/react";

function SignOutBTN() {
  return (
    <button
      onClick={() => signOut({ callbackUrl: "/signin" })}
      type="submit"
      className="block px-2 py-1 bg-slate-200 text-gray-700 hover:bg-slate-100 transition-all rounded-md"
    >
      Sign Out
    </button>
  );
}

export default SignOutBTN;
  1. 连接数据库(db)并开发用户模型

/utils/db.ts

import mongoose from "mongoose";

async function connect(): Promise<void> {
  try {
    await mongoose.connect("mongodb://127.0.0.1:27017/test");

    console.log("Connected.");
  } catch (err) {
    console.log(err);

    throw new Error("Faild To Connect.");
  }
}

const db = { connect };

export default db;

/models/user.model.ts

import mongoose, { Document } from "mongoose";

import { User as UT } from "@/interface/User";

interface UserSchema extends UT, Document {}

const userSchema = new mongoose.Schema<UserSchema>({
  username: { type: String, required: true },
  email: { type: String, required: true, unique: true },
  password: { type: String, required: true },
  isAdmin: { type: Boolean, required: true, default: false },
});

const User =
  mongoose.models.User || mongoose.model<UserSchema>("User", userSchema);

export default User;
  1. 开发用户静态数据和API(Route Handler)

/data/users.ts

import bcrypt from "bcryptjs";

const users = [
  {
    username: "P_Co_ST",
    email: "[email protected]",
    password: bcrypt.hashSync("654321"),
    isAdmin: true,
  },
  {
    username: "Harchi",
    email: "[email protected]",
    password: bcrypt.hashSync("123456"),
    isAdmin: false,
  },
];

export default users;

/app/api/user/route.ts

import { NextRequest, NextResponse } from "next/server";

import db from "@/utils/db";

import User from "@/models/user.model";

import users from "@/data/users";

export async function GET(request: NextRequest) {
  await db.connect();

  const result = await User.insertMany(users);

  return NextResponse.json(result);
}
  1. 实施 NextAuth

声明模块
next-auth
next-auth/jwt

/types/next-auth.d.ts

import { Session } from "next-auth";

import { JWT } from "next-auth/jwt";

import mongoose from "mongoose";

declare module "next-auth" {
  interface Session {
    _id: mongoose.Schema.Types.ObjectId;
    username: string;
    email: string;
    isAdmin: boolean;
  }

  interface User {
    _id: mongoose.Schema.Types.ObjectId;
    username: string;
    email: string;
    password: string;
    isAdmin: boolean;
  }
}

declare module "next-auth/jwt" {
  interface JWT {
    _id: mongoose.Schema.Types.ObjectId;
    username: string;
    email: string;
    isAdmin: boolean;
  }
}

/auth.ts

import NextAuth, { NextAuthConfig, User as UT } from "next-auth";

import Credentials from "next-auth/providers/credentials";

import db from "@/utils/db";

import User from "@/models/user.model";

import bcrypt from "bcryptjs";

const authOptions: NextAuthConfig = {
  secret: process.env.AUTH_SECRET,
  session: {
    strategy: "jwt",
  },
  callbacks: {
    async jwt({ token, user }) {
      if (user?._id) token._id = user._id;

      if (user?.username) token.username = user.username;

      if (user?.isAdmin) token.isAdmin = user.isAdmin;

      return token;
    },
    async session({ session, token }) {
      if (token?._id) session.user._id = token._id;

      if (token?.username) session.user.username = token.username;

      if (token?.isAdmin) session.user.isAdmin = token.isAdmin;

      return session;
    },
  },
  providers: [
    Credentials({
      name: "User",
      credentials: {
        email: {
          label: "Email",
          type: "email",
        },
        password: {
          label: "Password",
          type: "password",
        },
      },

      async authorize(credentials): Promise<UT> {
        await db.connect();

        const user = await User.findOne({ email: credentials.email });

        if (
          user &&
          bcrypt.compareSync(credentials.password as string, user.password)
        )
          return user;

        throw new Error("Invalid Email Or Password, Please Try Again.");
      },
    }),
  ],
};

export const { handlers, signIn, signOut, auth } = NextAuth(authOptions);

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

import { handlers } from "@/auth";

export const { GET, POST } = handlers;
mongoose next.js middleware next-auth auth.js
1个回答
0
投票

套餐

  1. npx create-next-app@latest app
  2. npm install zod
  3. npm install mongoose
  4. npm install next-auth
  5. npm install bcryptjs @types/bcryptjs

步骤

  1. 开发登录和注销页面并实现带有表单验证的前端表单

/app/signin/page.tsx

import SignInForm from "@/components/SignInForm";

import { Metadata } from "next";

export async function generateMetadata(): Promise<Metadata> {
  return {
    title: "Sign In",
  };
}

async function SignIn() {
  return (
    <div className="gird grid-cols-1 justify-items-center content-center h-full">
      <SignInForm />
    </div>
  );
}

export default SignIn;

/components/SignInForm.tsx

"use client";

import { z } from "zod";

import { useEffect, useState } from "react";

import { signIn } from "next-auth/react";

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

import { useRouter, useSearchParams } from "next/navigation";

const userSchema = z.object({
  email: z.string().email("Invalid Email."),
  password: z.string().min(5, "Password Must Be Least At 5 Character long."),
});

type User = z.infer<typeof userSchema>;

function SignInForm() {
  const [err, setErr] = useState<{
    email: string | undefined;
    password: string | undefined;
  }>({ email: undefined, password: undefined });

  const { data: session } = useSession();

  const router = useRouter();

  const redirect = useSearchParams().get("redirect");

  async function submitHandler(formData: FormData) {
    const email = formData.get("email") as string | undefined;
    const password = formData.get("password") as string | undefined;

    if (!email || !password)
      return setErr({
        email: email ? undefined : "Please Enter Your Email.",
        password: password ? undefined : "Please Enter Your Password.",
      });

    const user: User = { email, password };

    const result = userSchema.safeParse(user);

    if (result.error) {
      const zodErrors = result.error.errors;

      const email = zodErrors.find(
        (item) => item.path.join("") === "email"
      )?.message;
      const password = zodErrors.find(
        (item) => item.path.join("") === "password"
      )?.message;

      const errors = { email, password };

      setErr(errors);

      return;
    }

    setErr({ email: undefined, password: undefined });

    try {
      const res = await signIn("credentials", {
        redirect: false,
        email,
        password,
      });

      if (res?.error) console.log("Faild To Sign In.");
    } catch (err) {
      console.log(err);
    }
  }

  useEffect(() => {
    if (session?.user) router.push("/signout");
  }, [session, router, redirect]);

  return (
    <form
      action={submitHandler}
      className="bg-slate-100 text-gray-700 p-4 rounded-md"
    >
      <h2 className="mb-2">Sign IN</h2>
      <div>
        <label htmlFor="email">Email:</label>
        <br />
        <input
          type="email"
          id="email"
          name="email"
          className="px-2 py-1 my-1 bg-transparent border border-gray-600 rounded-md"
          placeholder="Enter Your Email"
          autoFocus
        />
        {err.email && <small className="block text-red-500">{err.email}</small>}
      </div>
      <div>
        <label htmlFor="password">Password:</label>
        <br />
        <input
          type="password"
          id="password"
          name="password"
          className="px-2 py-1 my-1 bg-transparent border border-gray-600 rounded-md"
          placeholder="Enter Your Password"
          autoFocus
        />
        {err.password && (
          <small className="block text-red-500">{err.password}</small>
        )}
      </div>
      <button
        type="submit"
        className="block mx-auto px-2 py-1 mt-4 bg-gray-600 text-slate-100 hover:bg-slate-700 transition-all rounded-md"
      >
        Submit
      </button>
    </form>
  );
}

export default SignInForm;

/app/signout/page.tsx

import SignOutBTN from "@/components/SignOutBTN";

import { Metadata } from "next";

export async function generateMetadata(): Promise<Metadata> {
  return {
    title: "Sign Out",
  };
}

function SignOut() {
  return (
    <div className="gird grid-cols-1 justify-items-center content-center h-full">
      <SignOutBTN />
    </div>
  );
}

export default SignOut;

/components/SignOutBTN.tsx

"use client";

import { signOut } from "next-auth/react";

function SignOutBTN() {
  return (
    <button
      onClick={() => signOut({ callbackUrl: "/signin" })}
      type="submit"
      className="block px-2 py-1 bg-slate-200 text-gray-700 hover:bg-slate-100 transition-all rounded-md"
    >
      Sign Out
    </button>
  );
}

export default SignOutBTN;
  1. 连接数据库(db)并开发用户模型

/utils/db.ts

import mongoose from "mongoose";

async function connect(): Promise<void> {
  try {
    await mongoose.connect("mongodb://127.0.0.1:27017/test");

    console.log("Connected.");
  } catch (err) {
    console.log(err);

    throw new Error("Faild To Connect.");
  }
}

const db = { connect };

export default db;

/models/user.model.ts

import mongoose, { Document } from "mongoose";

import { User as UT } from "@/interface/User";

interface UserSchema extends UT, Document {}

const userSchema = new mongoose.Schema<UserSchema>({
  username: { type: String, required: true },
  email: { type: String, required: true, unique: true },
  password: { type: String, required: true },
  isAdmin: { type: Boolean, required: true, default: false },
});

const User =
  mongoose.models.User || mongoose.model<UserSchema>("User", userSchema);

export default User;
  1. 开发用户静态数据和API(Route Handler)

/data/users.ts

import bcrypt from "bcryptjs";

const users = [
  {
    username: "P_Co_ST",
    email: "[email protected]",
    password: bcrypt.hashSync("654321"),
    isAdmin: true,
  },
  {
    username: "Harchi",
    email: "[email protected]",
    password: bcrypt.hashSync("123456"),
    isAdmin: false,
  },
];

export default users;

/app/api/user/route.ts

import { NextRequest, NextResponse } from "next/server";

import db from "@/utils/db";

import User from "@/models/user.model";

import users from "@/data/users";

export async function GET(request: NextRequest) {
  await db.connect();

  const result = await User.insertMany(users);

  return NextResponse.json(result);
}
  1. 实施 NextAuth

声明模块
next-auth
next-auth/jwt

/types/next-auth.d.ts

import { Session } from "next-auth";

import { JWT } from "next-auth/jwt";

import mongoose from "mongoose";

declare module "next-auth" {
  interface Session {
    _id: mongoose.Schema.Types.ObjectId;
    username: string;
    email: string;
    isAdmin: boolean;
  }

  interface User {
    _id: mongoose.Schema.Types.ObjectId;
    username: string;
    email: string;
    password: string;
    isAdmin: boolean;
  }
}

declare module "next-auth/jwt" {
  interface JWT {
    _id: mongoose.Schema.Types.ObjectId;
    username: string;
    email: string;
    isAdmin: boolean;
  }
}

/auth.ts

import NextAuth, { NextAuthConfig, User as UT } from "next-auth";

import Credentials from "next-auth/providers/credentials";

import db from "@/utils/db";

import User from "@/models/user.model";

import bcrypt from "bcryptjs";

const authOptions: NextAuthConfig = {
  secret: process.env.AUTH_SECRET,
  session: {
    strategy: "jwt",
  },
  callbacks: {
    async jwt({ token, user }) {
      if (user?._id) token._id = user._id;

      if (user?.username) token.username = user.username;

      if (user?.isAdmin) token.isAdmin = user.isAdmin;

      return token;
    },
    async session({ session, token }) {
      if (token?._id) session.user._id = token._id;

      if (token?.username) session.user.username = token.username;

      if (token?.isAdmin) session.user.isAdmin = token.isAdmin;

      return session;
    },
  },
  providers: [
    Credentials({
      name: "User",
      credentials: {
        email: {
          label: "Email",
          type: "email",
        },
        password: {
          label: "Password",
          type: "password",
        },
      },

      async authorize(credentials): Promise<UT> {
        await db.connect();

        const user = await User.findOne({ email: credentials.email });

        if (
          user &&
          bcrypt.compareSync(credentials.password as string, user.password)
        )
          return user;

        throw new Error("Invalid Email Or Password, Please Try Again.");
      },
    }),
  ],
};

export const { handlers, signIn, signOut, auth } = NextAuth(authOptions);

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

import { handlers } from "@/auth";

export const { GET, POST } = handlers;
© www.soinside.com 2019 - 2024. All rights reserved.