如何使用next-auth v5和Next js实现受保护的路由14/15

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

我想获取中间件中任何请求的登录状态,并且我想将其重定向到受保护的路由或登录路由。

但我没有在

middleware.ts
内进行会话。我使用了这段代码,但仍然显示错误。

//auth.ts

import NextAuth from "next-auth"
 
export const { auth, handlers } = NextAuth({
  callbacks: {
    authorized: async ({ auth }) => {
      // Logged in users are authenticated, otherwise redirect to login page
      return !!auth
    },
  },
})
// middleware.ts

import { auth } from "@/auth"
 
export default auth((req) => {
  if (!req.auth && req.nextUrl.pathname !== "/login") {
    const newUrl = new URL("/login", req.nextUrl.origin)
    return Response.redirect(newUrl)
  }
})

显示这样的错误 enter image description here

reactjs node.js next.js next-auth web-frontend
1个回答
0
投票

对于初学者,请迁移到 AuthJS v5。其次,问题似乎不是来自下一个身份验证,而是您没有安装 aws-sdk,这就是错误的原因。

假设您正在实施 RBAC(基于角色的访问控制),这个中间件可以为您工作:

//middleware.ts
// Importing necessary functions from the "next/server" module
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";

// Importing the auth function from our auth.ts file
import { auth } from "@/auth";

// Defining the protected routes with the roles allowed to access them, including dynamic segments
const protectedRoutes = [
  { path: "/admin/:path", roles: ["ADMIN"] }, // Only ADMIN can access this route
  { path: "/user/:path", roles: ["ADMIN", "USER"] }, // Both ADMIN  and USER can access this route
];

// Function to find the current route in the protectedRoutes array
const findCurrentRoute = (pathname: string) => {
  return protectedRoutes.find((route) =>
    new RegExp(`^${route.path.replace(/:\w+/g, "\\w+")}$`).test(pathname)
  );
};

// Middleware function to handle requests
export default async function middleware(request: NextRequest) {
  // Get the session using the auth function
  const session = await auth();

  // Find the current route in the protectedRoutes array
  const currentRoute = findCurrentRoute(request.nextUrl.pathname);

  // Check if the request path is protected
  if (currentRoute) {
    // If the route is protected and there's no session, redirect to the login page
    if (!session) {
      const absoluteURL = new URL("/auth/login", request.nextUrl.origin);
      return NextResponse.redirect(absoluteURL.toString());
    }

    // If the user role is not allowed for the current route, redirect to the unauthorized page
    if (!currentRoute.roles.includes(session.user.role)) {
      const absoluteURL = new URL("/unauthorized", request.nextUrl.origin);
      return NextResponse.redirect(absoluteURL.toString());
    }
  }

  // If the route is not protected or the user has access, continue to the next middleware or the requested page
  return NextResponse.next();
}

// Configuration to match all paths except for certain static files and API routes
export const config = {
  matcher: ["/((?!api|_next/static|_next/image|favicon.ico).*)"],
};

此外,这里还有一个初学者级别配置的 auth.config.ts 和 auth.ts,具有 Github、Google 和基于凭据的身份验证。如果您要在 vercel 上部署,则兼容边缘情况。

//auth.config.ts
import Github from "next-auth/providers/github";
import Google from "next-auth/providers/google";
import { type NextAuthConfig } from "next-auth";
import Credentials from "next-auth/providers/credentials";
import bcrypt from "bcrypt-edge";
import { prisma } from "./prisma";

// Adding the callbacks for managing the session and token
export default {
  providers: [
    Github({
      clientId: process.env.AUTH_GITHUB_ID,
      clientSecret: process.env.AUTH_GITHUB_SECRET,
      allowDangerousEmailAccountLinking: true
    }),
    Google({
      clientId: process.env.GOOGLE_CLIENT_ID,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET,
      allowDangerousEmailAccountLinking: true
    }),
    Credentials({
      name: "Credentials",
      credentials: {
        email: {
          label: "Email",
          type: "email",
          placeholder: "[email protected]",
        },
        password: { label: "Password", type: "password" },
      },
      authorize: async (credentials) => {
        if (!credentials || !credentials.email || !credentials.password) {
          return null;
        }

        const email = credentials.email as string;

        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        const user: any = await prisma.user.findUnique({
          where: {
            email,
          },
        });

        if (!user) {
          throw new Error("No account found with this email.");
        } else {
          const isMatch = bcrypt.compareSync(
            credentials.password as string,
            user.hashedPassword
          );
          if (!isMatch) {
            throw new Error("Incorrect password.");
          }
        }

        // Return user object with role information
        return { ...user, role: user.role };
      },
    }),
  ],
  callbacks: {
    // Callback to manage the JWT token
    jwt: async ({ token, user }) => {
      if (user) {
        token.id = user.id;
        token.role = user.role;
      }
      return token;
    },
    // Callback to manage the session object
    session: async ({ session, token }) => {
      if (token) {
        session.user.id = token.id as string;
        session.user.role = token.role as string;
      }
      return session;
    },
  },
  // Additional configuration can go here (e.g., pages)
  pages: {
    signIn: "/sign-in",
  },
} satisfies NextAuthConfig;
//auth.ts
import NextAuth from "next-auth";
import { PrismaAdapter } from "@auth/prisma-adapter"
import { prisma } from "./prisma"
import authConfig from "./auth.config";

export const {
  handlers: { GET, POST },
  signIn,
  signOut,
  auth,
} = NextAuth({
  adapter: PrismaAdapter(prisma),
  session: { strategy: "jwt" },
  ...authConfig,
});

以及使用 bcrypt (bcrypt-edge) 进行加盐和散列的代码:

import bcrypt from "bcrypt-edge";
export function saltAndHashPassword(password: any) {
  const saltRounds = 10; // Adjust the cost factor according to your security requirements
  const salt = bcrypt.genSaltSync(saltRounds); // Synchronously generate a salt
  const hash = bcrypt.hashSync(password, salt); // Synchronously hash the password
  return hash; // Return the hash directly as a string
}

您可能会在 auth.config.ts 中收到 TypeScript 错误 RBAC,修复方法如下:

  • 创建类型文件夹
  • 添加名为“next-auth.d.ts”的文件
  • 并将此代码放入其中:
import { DefaultSession, DefaultUser } from "@auth";

// Extend the User interface to include role
declare module "next-auth" {
  interface User extends DefaultUser {
    role?: string;
  }

  interface Session {
    user?: {
      id?: string;
      role?: string;
    } & DefaultSession["USER"];
  }
}

确保您的用户确实具有角色字段。示例 schema.prisma:

model User {
  id             String          @id @default(auto()) @map("_id") @db.ObjectId
  name           String?
  email          String?         @unique
  hashedPassword String?
  emailVerified  DateTime?
  image          String?
  accounts       Account[]
  sessions       Session[]
  role           UserRole?       @default(USER)
  Authenticator  Authenticator[]

  createdAt      DateTime @default(now())
  updatedAt      DateTime @updatedAt

  // Relations
  blogPosts      BlogPost[]
}

此外,您还必须确保您的数据库具有这些工作所需的架构,您可以在此处获取这些(选择数据库的连接方法):AuthJS v5 数据库适配器

© www.soinside.com 2019 - 2024. All rights reserved.