【Next.js 入门教程系列】07-身份验证

原文链接

CSDN 的排版/样式可能有问题,去我的博客查看原文系列吧,觉得有用的话, 给我的点个star,关注一下吧

上一篇【Next.js 入门教程系列】06-上传文件

身份验证

本篇包括以下内容:

  • Setting up Next Auth
  • Configuring the Google Provider
  • Authentication sessions
  • Protecting routes
  • Database adapters
  • Configuring the Credentials Provider

Setting up Next Auth

使用 npm install next-auth 安装 Next-Auth(Auth.js)。安装好后首先去 .env 中配置环境变量

复制代码
DATABASE_URL="mysql://root:@localhost:3306/nextapp"
NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME="dzwywfjst"
# 添加下面两行
NEXTAUTH_URL=http:localhost:5050 # 你的项目位置
NEXTAUTH_SECRET=5xNi+cY1LdL1YnBWD9cUi4A34tTZJfUjKTlMCMjFcL0= # 随机数,可以使用 openssl random -base64 32 生成

设置完成后,在 api 文件夹下添加 auth/[...nextauth]/route.ts,并添加以下内容,为接下来做准备

api\auth[...nextauth]\route.ts

复制代码
import NextAuth from "next-auth/next";

const handler = NextAuth({});

export { handler as GET, handler as POST };

设置 Provider

本章代码链接

Next Auth Provider可以看到 Next-Auth 支持多个 Authenticator Provider,包括 GoogleGithubFacebook

创建项目

此处以 Google 为例,首先进入 Google Credentials 页面新建一个 Project

设置项目名称后点击创建即可

如下图,点击配置 Consent Screen

首先设置为供外部使用

在弹出页面填写三个必须字段 应用名称用户支持电子邮件,以及最下面的开发者联系信息,其他字段都可选填,比如图片 logo 之类的

点击页面最下面的保存并继续,设置应用权限,一般只添加 email 和 profile 即可。再点击保存并继续,添加测试用户,把自己的账户添加即可。

最后保存返回信息中心即可

创建 OAuth

凭据 页面点击创建 OAuth 客户端 ID

设置 OAuth,首先设置为 Web 应用,设置应用名,下方 JavaScript 来源设置为部署的端口(开发环境),再下方设置重定向的 URI,在Next Auth Google页面有写

点击创建即可

复制好客户端 id 和密钥备用

调用

回到 .env 中添加 GOOGLE_CLIENT_IDGOOGLE_CLIENT_SECRET

复制代码
DATABASE_URL="mysql://root:@localhost:3306/nextapp"
NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME="dzwywfjst"
# 添加下面两行
NEXTAUTH_URL=http:localhost:5050 # 你的项目位置
NEXTAUTH_SECRET=5xNi+cY1LdL1YnBWD9cUi4A34tTZJfUjKTlMCMjFcL0= # 随机数,可以使用 openssl random -base64 32 生成
# 刚刚的客户端id 和 密钥
GOOGLE_CLIENT_ID=479267153395-gpqe25rbf62p0aj4h7icnfd01qt0p3qo.apps.googleusercontent.com
GOOGLE_CLIENT_SECRET=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

在 auth/[...nextauth]/route.ts,并添加以下内容(直接复制即可)

api\auth[...nextauth]\route.ts

复制代码
import NextAuth from "next-auth";
import GoogleProvider from "next-auth/providers/google";

const handler = NextAuth({
  providers: [
    GoogleProvider({
      clientId: process.env.GOOGLE_CLIENT_ID!,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
    }),
  ],
});

export { handler as GET, handler as POST };

在 Navbar.tsx 中添加一个 Link 到 login

NavBar.tsx

复制代码
import Link from "next/link";
import React from "react";

const NavBar = () => {
  return (
    <div className="flex bg-slate-200 p-5 space-x-3">
      <Link href="/" className="mr-5">
        Next.js
      </Link>
      <Link href="/users">Users</Link>
      {/* Add this Link */}
      <Link href="/api/auth/signin">Login</Link>
    </div>
  );
};

export default NavBar;

回到浏览器,点击 login,即可看到如下页面


由于某些魔法原因,next-auth 使用 google 一直在报 ERROR,笔者寻找多方也未解决,尝试使用 Github Provider 可行,故向读者推荐使用 Github Provider,并在下面给出演示

复制代码
[next-auth][error][SIGNIN_OAUTH_ERROR]
https://next-auth.js.org/errors#signin_oauth_error outgoing request timed out after 3500ms {
  error: {
    message: 'outgoing request timed out after 3500ms',
    stack: 'RPError: outgoing request timed out after 3500ms\n' +
      .......
  name: 'RPError'
  },
  providerId: 'google',
  message: 'outgoing request timed out after 3500ms'
}

Github Provider

进入Github OAuth App页面,新建 OAuth App

设置内容和 Google 内容差不多,Callback url 最后换成 Github 即可 http://localhost:5050/api/auth/callback/github

点击创建凭证

IMPORTANT

注意,创建好后仅能在该页面复制一次,以后再也无法复制

回到 .env 中添加 GITHUB_CLIENT_ID 和 GITHUB_CLIENT_SECRET

复制代码
DATABASE_URL="mysql://root:@localhost:3306/nextapp"
NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME="dzwywfjst"
# 添加下面两行
NEXTAUTH_URL=http:localhost:5050 # 你的项目位置
NEXTAUTH_SECRET=5xNi+cY1LdL1YnBWD9cUi4A34tTZJfUjKTlMCMjFcL0= # 随机数,可以使用 openssl random -base64 32 生成
# 刚刚的客户端id 和 密钥
GITHUB_CLIENT_ID=6f1433456dsfa526c
GITHUB_CLIENT_SECRET=ec8055183a9adfefsf0b305c282be6d55fe64f

修改 auth/[...nextauth]/route.ts 中的 provider 为 Github

复制代码
import NextAuth from "next-auth";
import Github from "next-auth/providers/github";

const handler = NextAuth({
  providers: [
    Github({
      clientId: process.env.GITHUB_CLIENT_ID!,
      clientSecret: process.env.GITHUB_CLIENT_SECRET!,
    }),
  ],
});

export { handler as GET, handler as POST };

保存,进入 Login 界面即可正常使用 Github 登录

Session

Check Session

本章代码链接

在浏览器中打开开发者工具,进入应用,选择 cookie,即可看到这里有一条 next-auth.session-token ,其本质为一个 json web token,为了查看这个 cookie,我们可以在 /api/auth 下新建 token/route.ts,并添加以下内容

复制代码
import { getToken } from "next-auth/jwt";
import { NextRequest, NextResponse } from "next/server";

export async function GET(request: NextRequest) {
  const token = await getToken({ req: request });
  return NextResponse.json(token);
}

去浏览器访问 http://localhost:5050/api/auth/token 即可看到如下内容

其包含如用户名,邮箱,头像,过期时间等信息

Accessing Session from client

本章代码链接

在用户端获取 session 信息需要用到 SessionProvider。我们首先创建一个新 component /auth/AuthProvider.tsx。将所有 children 用 SessionProvider 包起来

复制代码
"use client";
import React, { ReactNode } from "react";
import { SessionProvider } from "next-auth/react";

const AuthProvider = ({ children }: { children: ReactNode }) => {
  return (
    <>
      <SessionProvider>{children}</SessionProvider>
    </>
  );
};
export default AuthProvider;

然后回到根文件夹的 layout.tsx

复制代码
  import "./globals.css";
  import type { Metadata } from "next";
  import { Inter } from "next/font/google";
  import NavBar from "./NavBar";
// 导入刚刚的 AuthProvider
+ import AuthProvider from "./auth/Provider";

  const inter = Inter({ subsets: ["latin"] });

  export const metadata: Metadata = {
    title: "Create Next App",
    description: "Generated by create next app",
  };

  export default function RootLayout({
    children,
  }: {
    children: React.ReactNode;
  }) {
    return (
      <html lang="en" data-theme="winter">
        <body className={inter.className}>
{/*将 body 里的内容都用 AuthProvider 包起来*/}
+         <AuthProvider>
            <NavBar />
            <main className="p-5">{children}</main>
+         </AuthProvider>
        </body>
      </html>
    );
  }

之后到 NavBar 组件里获取 Session 内容

复制代码
  "use client";
  // improt useSession
+ import { useSession } from "next-auth/react";
  import Link from "next/link";
  import React from "react";

  const NavBar = () => {
  // 使用 useSession() 来获取 Session 中的数据
+   const { status, data: session } = useSession();

    return (
      <div className="flex bg-slate-200 p-5 space-x-3">
        <Link href="/" className="mr-5">
          Next.js
        </Link>
        <Link href="/users">Users</Link>
  {/*根据status的不同状态来渲染 "登录" 或者 "用户" 或 "加载中"*/}
+       {status === "loading" && <div>Loading...</div>}
+       {status === "authenticated" && <div>{session.user!.name}</div>}
+       {status === "unauthenticated" && (
+         <Link href="/api/auth/signin">Login</Link>
+       )}
      </div>
    );
  };

  export default NavBar;

此时回到浏览器,刷新即可看到,可以正常显示用户名了

Accessing Session from server

本章代码链接

在服务器端获取 session 也很简单,首先要先回到 api/auth/[...nextauth]/route.ts 中修改一下,将刚刚的 providers 作为一个 const export 出来,以便在其他页面使用(注意笔者使用的还是 Github 作为 Provider)

复制代码
import NextAuth from "next-auth";
import Github from "next-auth/providers/github";
// import GoogleProvider from "next-auth/providers/google";

export const authOptions = {
  providers: [
    Github({
      clientId: process.env.GITHUB_CLIENT_ID!,
      clientSecret: process.env.GITHUB_CLIENT_SECRET!,
    }),
  ],
};

const handler = NextAuth(authOptions);

export { handler as GET, handler as POST };

然后在主页面的 page.tsx 中可以调用 getServerSession() 来获取 session

复制代码
  import Link from "next/link";
  import ProductCard from "./components/ProductCard/ProductCard";
  // 导入 getServerSession 方法和刚刚的 authOptions 设置
+ import { getServerSession } from "next-auth";
+ import { authOptions } from "./api/auth/[...nextauth]/route";

  export default async function Home() {
  // 调用 getServerSession 来获取 session
+   const session = await getServerSession(authOptions);
    return (
      <>
        <main>
  {/*直接调用session中的内容(user!中的!代表该变量不会为空)"*/}
+         <h1>Hello {session && session.user!.name}!</h1>
          <Link href="/users">Users</Link>
          <ProductCard />
        </main>
      </>
    );
  }

最终显示效果如下

Sign Out

本章代码链接

使用一个 Link 跳转到 api/auth/signout 即可

复制代码
  "use client";

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

  const NavBar = () => {
    const { status, data: session } = useSession();

    return (
      <div className="flex bg-slate-200 p-5 space-x-3">
        <Link href="/" className="mr-5">
          Next.js
        </Link>
        <Link href="/users">Users</Link>
        {status === "loading" && <div>Loading...</div>}
        {/*跳转至 api/auth/signout 即可*/}
+       {status === "authenticated" && (
+         <div>
+           {session.user!.name}
+           <Link href="api/auth/signout" className="ml-3">
+             Sign Out
+           </Link>
+         </div>
+       )}
        {status === "unauthenticated" && (
          <Link href="/api/auth/signin">Login</Link>
        )}
      </div>
    );
  };

  export default NavBar;

最终效果如下

Protecting Route

本章代码链接

有时候,我们需要防止用户在没有登录的情况下跳转至某些页面,比如想要直接使用 url 进入到 profile 页面,此时我们需要重定向到登录界面。在 Next.js 中内置了 MiddleWare 帮我们完成这个任务,我们不需要手动在每个界面自己写跳转。我们只需要在根目录(注意是和 app 同级目录,之前都是在 app 文件夹中)下添加 middleware.ts 添加设置即可,如下代码则表示所有以 /dashboard 开头的路由,都需要有 session。其最后一个字符代表子路由的层级

复制代码
export { default } from "next-auth/middleware";

export const config = {
  // *: zero or more
  // +: one or more
  // ?: zero or one
  matcher: ["/dashboard/:path*"],
};

Database Adapters

本章代码链接

Next-Auth Prisma页面可以找到,使用 npm i @next-auth/prisma-adapter 以安装 adapter

备注

Next-Auth 正在改名为 Auth.js,截止 2024.2.27,仍然可以使用上方 npm 指令安装,如果读者使用时出错,访问Next-Auth Prisma页面应该可以找到新的安装命令

安装好之后,配置 schema.prisma,同样在Next-Auth Prisma Schema页面可以找到教程,也可以把我下面的代码直接复制到 schema.prisma 中,再使用 npx prisma migrate dev 指令进行合并即可

schema.prisma配置

复制代码
// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema

// Looking for ways to speed up your queries, or scale easily with your serverless or edge functions?
// Try Prisma Accelerate: https://pris.ly/cli/accelerate-init

generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider = "mysql"
  url      = env("DATABASE_URL")
}

model Account {
  id                 String  @id @default(cuid())
  userId             String
  type               String
  provider           String
  providerAccountId  String
  refresh_token      String?  @db.Text
  access_token       String?  @db.Text
  expires_at         Int?
  token_type         String?
  scope              String?
  id_token           String?  @db.Text
  session_state      String?

  user User @relation(fields: [userId], references: [id], onDelete: Cascade)

  @@unique([provider, providerAccountId])
}

model Session {
  id           String   @id @default(cuid())
  sessionToken String   @unique
  userId       String
  expires      DateTime
  user         User     @relation(fields: [userId], references: [id], onDelete: Cascade)
}

model User {
  id            String    @id @default(cuid())
  name          String?
  email         String?   @unique
  emailVerified DateTime?
  image         String?
  accounts      Account[]
  sessions      Session[]
}

model VerificationToken {
  identifier String
  token      String   @unique
  expires    DateTime

  @@unique([identifier, token])
}

完成后在 api\auth[...nextauth]\route.ts 设置 PrismaAdapter

复制代码
  import NextAuth, { NextAuthOptions } from "next-auth";
  import Github from "next-auth/providers/github";
  import GoogleProvider from "next-auth/providers/google";
+ import { PrismaAdapter } from "@next-auth/prisma-adapter";
+ import prisma from "@/prisma/client";

  export const authOptions: NextAuthOptions = {
  // 设置 PrismaAdapter
+   adapter: PrismaAdapter(prisma),
    providers: [
      GoogleProvider({
        clientId: process.env.GOOGLE_CLIENT_ID!,
        clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
      }),
      Github({
        clientId: process.env.GITHUB_CLIENT_ID!,
        clientSecret: process.env.GITHUB_CLIENT_SECRET!,
      }),
    ],
    session: {
      strategy: "jwt",
    },
  };

  const handler = NextAuth(authOptions);

  export { handler as GET, handler as POST };

再次尝试登陆后,即可在数据库中看到

CSDN 的排版/样式可能有问题,去我的博客查看原文系列吧,觉得有用的话, 给我的点个star,关注一下吧

下一篇讲发送邮件

下一篇【Next.js 入门教程系列】08-发送邮件

相关推荐
martian6652 分钟前
达梦数据库中无效触发器的排查与解决方案指南
开发语言·数据库
网硕互联的小客服4 分钟前
RAID 阵列有哪些?分别有什么作用?
运维·服务器·网络·数据库·网络安全·raid
noravinsc2 小时前
django filter 统计数量 按属性去重
数据库·django·sqlite
Estar.Lee8 小时前
MySQL中外键约束详解 外键在表关系维护中的作用
运维·数据库·mysql·api·免费api
灯琰18 小时前
五款MySQL 可视化客户端软件
数据库·mysql
两袖清风9988 小时前
【MySQL】三大范式
数据库·mysql
Wooden-Flute10 小时前
八、数据库恢复技术
服务器·数据库·oracle
南棱笑笑生10 小时前
20250611在全志H3平台的Nano Pi NEO CORE开发板上运行Ubuntu Core16.04时让4G模块EC200A-CN使用AT命令拨号上网
linux·数据库·ubuntu
文牧之11 小时前
PostgreSQL 的扩展pg_surgery
运维·数据库·postgresql