技术文献

next.js16的next-auth插件的Credentials方式的后台登录和认证

2026-05-31

Next.js 16 + NextAuth Credentials 表单登录完整案例

豆包:https://www.doubao.com/chat/38428026830768386

 

环境说明:

  • Next.js 16(App Router)
  • next-auth@5(Credentials 凭证登录,无数据库,内存模拟用户)
  • 前台判断登录状态、获取用户名
  • 后端 API 路由判断登录、获取用户名

1. 安装依赖

npm install next@16 next-auth@5 bcryptjs
npm install -D @types/bcryptjs

2. 配置 NextAuth 认证(核心)

新建文件:app/api/auth/[...nextauth]/route.ts

import NextAuth, { NextAuthOptions } from "next-auth";
import CredentialsProvider from "next-auth/providers/credentials";
// 模拟用户库(实际用数据库)
const mockUser = {
  id: "1",
  name: "张三",
  email: "zhangsan@test.com",
  password: "123456",
};
export const authOptions: NextAuthOptions = {
  providers: [
    CredentialsProvider({
      name: "Credentials",
      credentials: {
        username: { label: "用户名", type: "text" },
        password: { label: "密码", type: "password" },
      },
      async authorize(credentials) {
        // 表单提交校验
        if (!credentials?.username || !credentials?.password) return null;
        // 模拟登录校验
        if (
          credentials.username === mockUser.name &&
          credentials.password === mockUser.password
        ) {
          return {
            id: mockUser.id,
            name: mockUser.name,
            email: mockUser.email,
          };
        }
        return null;
      },
    }),
  ],
  session: {
    strategy: "jwt", // 必须 jwt,Credentials 不支持数据库 session
  },
  callbacks: {
    // JWT 回调:把用户信息存入 token
    async jwt({ token, user }) {
      if (user) {
        token.id = user.id;
        token.name = user.name;
      }
      return token;
    },
    // Session 回调:把 token 信息暴露给前端
    async session({ session, token }) {
      if (token) {
        session.user.id = token.id as string;
        session.user.name = token.name as string;
      }
      return session;
    },
  },
  pages: {
    signIn: "/login", // 自定义登录页
  },
};
const handler = NextAuth(authOptions);
export { handler as GET, handler as POST };

 

3. 全局配置 Auth Provider(客户端共享登录状态)

新建:app/providers.tsx

"use client";
import { SessionProvider } from "next-auth/react";
export default function AuthProviders({
  children,
}: {
  children: React.ReactNode;
}) {
  return <SessionProvider>{children}</SessionProvider>;
}

 

修改根布局 app/layout.tsx

import type { Metadata } from "next";
import AuthProviders from "./providers";
export const metadata: Metadata = {
  title: "Next16 Credentials 登录案例",
};
export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="zh">
      <body>
        <AuthProviders>{children}</AuthProviders>
      </body>
    </html>
  );
}

 

4. 登录页面(表单提交)

新建:app/login/page.tsx

"use client";
import { signIn } from "next-auth/react";
import { useRouter } from "next/navigation";
import { useState } from "react";
export default function LoginPage() {
  const router = useRouter();
  const [error, setError] = useState("");
  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();
    const formData = new FormData(e.target as HTMLFormElement);
    const username = formData.get("username") as string;
    const password = formData.get("password") as string;
    // 调用 next-auth 登录
    const res = await signIn("credentials", {
      username,
      password,
      redirect: false,
    });
    if (res?.error) {
      setError("用户名或密码错误");
    } else {
      router.push("/"); // 登录成功跳首页
      router.refresh();
    }
  };
  return (
    <div style={{ padding: "2rem" }}>
      <h1>表单登录</h1>
      {error && <p style={{ color: "red" }}>{error}</p>}
      <form onSubmit={handleSubmit}>
        <div>
          <label>用户名:</label>
          <input name="username" defaultValue="张三" required />
        </div>
        <div style={{ marginTop: "0.5rem" }}>
          <label>密码:</label>
          <input name="password" type="password" defaultValue="123456" required />
        </div>
        <button type="submit" style={{ marginTop: "1rem" }}>
          登录
        </button>
      </form>
    </div>
  );
}

 

5. 首页:前台判断登录 + 获取用户名

app/page.tsx

"use client";
import { useSession, signOut } from "next-auth/react";
export default function Home() {
  const { data: session, status } = useSession();
  if (status === "loading") return <p>加载中...</p>;
  return (
    <div style={{ padding: "2rem" }}>
      <h1>首页 - 客户端获取登录信息</h1>
      {session ? (
        <>
          <p>✅ 已登录,用户名:{session.user?.name}</p>
          <button onClick={() => signOut({ callbackUrl: "/login" })}>
            退出登录
          </button>
          <p>
            <a href="/api/user-info">查看后端获取用户信息</a>
          </p>
        </>
      ) : (
        <p>❌ 未登录,请 <a href="/login">去登录</a></p>
      )}
    </div>
  );
}

 

6. 后端 API:服务端判断登录 + 获取用户名

新建:app/api/user-info/route.ts

import { getServerSession } from "next-auth/next";
import { authOptions } from "../auth/[...nextauth]/route";
export async function GET() {
  // 服务端获取 session
  const session = await getServerSession(authOptions);
  if (!session) {
    return Response.json({ isLogin: false, msg: "未登录" }, { status: 401 });
  }
  return Response.json({
    isLogin: true,
    username: session.user?.name,
    userId: session.user?.id,
  });
}

 

7. 运行与测试

  1. 启动项目:npm run dev
  2. 访问:http://localhost:3000/login
  3. 账号:张三,密码:123456
  4. 登录后:
    • 首页客户端展示用户名
    • 访问 /api/user-info 后端返回登录状态和用户名

关键知识点

  1. Credentials 必须使用 JWT 策略,不能用 database session
  2. 客户端:useSession() 判断登录、拿用户名
  3. 服务端:getServerSession(authOptions) 判断登录、拿用户名
  4. 登录 / 退出:signIn / signOut