Published on

如何使用 NextAuth.js 实现 Nextjs 的身份验证

引言

NextAuth.js 是一个用于 Next.js 的身份验证库。本文将以Nextjs App Router 为例,介绍如何使用 NextAuth.js 实现 Nextjs的身份验证。以及过程中遇到的问题和解决方案。 本文使用的版本分别为:

"next": "15.2.1",
"next-auth": "^4.24.11"

安装及配置

基本的安装及配置本文不做介绍,请参考NextAuth.js 官方文档。如果是使用 Nextjs App Router ,请参考NextAuth.js App Router Guide

核心的目录路径为:/app/api/auth/[...nextauth]/route.ts,这是 NextAuth.js 的默认路由路径,不能修改。 目录结构

其中代码分别如下:

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

import NextAuth from "next-auth";

import { authOptions } from "./config";

const handler = NextAuth(authOptions);

export { handler as GET, handler as POST };

这是 Nextjs 最新版本 App Router 的Api路由配置方式。

// /app/api/auth/config.ts
import { PrismaAdapter } from "@next-auth/prisma-adapter";
import prisma from "@/lib/prisma"; // 确保路径正确
import GoogleProvider from "next-auth/providers/google";
import GithubProvider from "next-auth/providers/github";

import type { NextAuthOptions } from "next-auth";

export interface SessionUser {
  id: string;
  name: string;
  email: string;
  image: string;
}

export const authOptions: NextAuthOptions = {
  adapter: PrismaAdapter(prisma),
  providers: [
    GoogleProvider({
      clientId: process.env.GOOGLE_CLIENT_ID as string,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET as string,
      async profile(profile) {
        console.log("Google Profile:", profile);
        return {
          id: profile.id.toString(),
          name: profile.name,
          email: profile.email,
          image: profile.image,
        };
      },
    }),
    GithubProvider({
      clientId: process.env.GITHUB_ID as string,
      clientSecret: process.env.GITHUB_SECRET as string,
      async profile(profile) {
        console.log("GitHub Profile:", profile);
        return {
          id: profile.id.toString(),
          name: profile.name,
          email: profile.email,
          image: profile.avatar_url,
        };
      },
    }),
    // 其他提供者...
  ],
  session: {
    strategy: "jwt",
    // maxAge: 30 * 24 * 60 * 60, // 30 days
    // updateAge: 24 * 60 * 60, // 24 hours
  },
  callbacks: {
    async session({ session, token }) {
      console.log("token in callback:", token);
      return { ...session, user: { ...session.user, id: token.sub } };
    },
    async jwt({ token, user }) {
      if (user) {
        token.id = user.id;
      }
      return token;
    },
  },
};

重点需要关注的为authOptions配置,这是 NextAuth.js 的配置选项。

adapter

adapter: PrismaAdapter(prisma)

让 NextAuth 用 Prisma 读写用户、session、account 等表。具体的表结构,字段等见下文。本地开发时,如果不配置 adapter,会在内存中创建用户,登录等信息,不会写入数据库。生产环境必须配置,才能持久化。

providers

GoogleProviderGithubProvider,可以添加多个 provider,如 GoogleProviderGithubProvider。需要在对应平台注册应用,获取clientIdclientSecret

  • clientId/clientSecret:从环境变量读取 OAuth 配置。

  • profile 方法:自定义 provider 返回的用户对象结构,确保有 id、name、email、image 字段。 Google: profile.image GitHub: profile.avatar_url

session

strategy: "jwt"

使用 JWT(JSON Web Token)无状态存储 session,而不是数据库 session。这里需要详细介绍一下 JWT 和 session 的概念。

  1. session 的作用 session 是用户登录后,前端和后端用来识别用户身份的一种机制。 在 NextAuth 里,session 用于保存用户的登录状态和部分用户信息,前端每次请求时可以带上 session,后端据此判断用户是谁。

  2. session.strategy 的选项 NextAuth 支持两种 session 策略:

    • jwt:无状态,基于 JSON Web Token(JWT)
    • database:有状态,基于数据库 session 表
  3. "jwt" 策略原理:

    用户登录后,NextAuth 生成一个 JWT(加密的字符串),包含用户信息。 JWT 存在客户端(通常是 cookie),每次请求时带上。 服务器通过密钥验证 JWT,解析出用户信息,无需查数据库。

    优点:

    • 无需频繁访问数据库,性能高,扩展性好。
    • 适合无服务器(serverless)架构。
    • 部署简单,session 不会丢失(因为都在客户端)。

    缺点:

    • JWT 体积较大,不能存太多敏感信息。
    • 不能主动失效(如强制登出),只能等过期。 如果密钥泄露,所有 session 都不安全。
  4. "database" 策略原理:

    用户登录后,NextAuth 在数据库 session 表里存一条记录,session id 存在 cookie。每次请求时,服务器查数据库,验证 session 是否有效。

    优点:

    • 可以主动失效 session(如后台踢人、登出)。
    • 可以存储更多信息,安全性高。

    缺点:

    • 每次请求都要查数据库,性能略低。
    • 需要维护 session 表,适合传统后端服务。

callbacks

callbacks 是 NextAuth 提供的钩子(hook),允许你在认证流程的关键节点自定义逻辑。 你可以用它们来修改 session、token、用户信息、跳转地址等,满足业务需求

常用的 callbacks 字段
  • signIn:用户登录时触发,可以用来允许/拒绝登录。
  • redirect:登录/登出/认证后跳转时触发,可以自定义跳转地址。
  • session:每次 session 被访问时触发,可以自定义 session 返回给前端的内容。
  • jwt:每次生成/更新 JWT 时触发,可以自定义 token 内容。
  • accountuser:OAuth 登录时触发,可以自定义用户/账号信息。

上述代码中用到的sessionjwt回调,作用分别如下:

  • session回调:
    • 作用: 每次前端调用 getSession() 或 useSession() 时触发。 可以自定义返回给前端的 session 内容。
    • 参数: session:默认的 session 对象(包含 user、expires 等)。 token:当前的 JWT token。
    • 返回值: 返回新的 session 对象。 上述代码把 token.sub(用户 id)加到 session.user.id 上,方便前端获取用户 id。
  • jwt回调:
    • 作用: 每次生成/更新 JWT 时触发。 可以自定义 token 内容(比如加上用户 id、角色等)。
    • 参数: token:当前的 token。 user:登录时的用户对象(只在登录时有)。
    • 返回值: 返回新的 token 对象。上述代码登录时把 user.id 存到 token.id 上,后续 session 回调可以用。

使用Session获取用户登录信息

当用户登录后,我们需要获取用户的登录信息,以便在页面中显示或做其他操作。NextAuth.js 提供了两种方式获取用户登录信息:

  • 客户端,使用useSession钩子在React 客户端组件中获取用户登录信息。
import { useSession } from "next-auth/react";

const { data: session } = useSession();

if (session) {
  console.log("Session:", session);
}
  • 服务端,使用getServerSession函数获取用户登录信息。
import { getServerSession } from "next-auth/next"
import { authOptions } from "./auth/[...nextauth]"

export default async (req, res) => {
  const session = await getServerSession(req, res, authOptions)

  if (session) {
    res.send({
      content:
        "This is protected content. You can access this content because you are signed in.",
    })
  } else {
    res.send({
      error: "You must be signed in to view the protected content on this page.",
    })
  }
}

详情见NextAuth.js Session中使用useSessiongetServerSession部分。

数据库设置

NextAuth.js 支持多种数据库,如 PostgreSQL、MySQL、MongoDB 等 ,也支持多种Adapter。本文使用 PostgreSQL 作为数据库,并使用 Prisma Adapter 连接数据库。

具体的步骤见Prisma Adapter,其中数据库表结构及Prisma Schema也在文档中,直接复制到项目中即可。然后按照文档步骤进行即可,生成Prisma Client,并配置到 NextAuth.js 的配置中。

踩坑:

  • 每次调用 /api/auth/xxxx下的路由都会报404错误。经过排查发现是中间件配置有问题。
// /app/middleware.ts
export const config = {
  // Matcher ignoring `/_next/` and `/api/`
  // matcher: ["/((?!api|_next/static|_next/image|favicon.ico).*)", "/:path*"], 错误配置,导致 /api/auth/xxxx 被拦截
  matcher: ["/((?!api|_next/static|_next/image|favicon.ico).*)"], // 正确配置,/api/auth/xxxx 不会被拦截
};

总结

整体步骤

  1. 安装 NextAuth.js 及依赖
  2. 配置 NextAuth.js 的配置文件
  3. 配置 Nextjs 的 Api 路由
  4. 安装数据库,配置数据库连接
  5. 安装Prisma,配置 Prisma Schema,生成 Prisma Client
  6. Nextjs 配置 Prisma Adapter
  7. 去对应平台注册应用,获取 clientId 和 clientSecret
  8. 运行项目,测试登录,登出,获取用户信息等功能