跳转至

NextAuth Session 更新

原文地址:https://mp.weixin.qq.com/s/Nu4st2UYlqmXcOBD1eIIHg

相较于之前的登录登出,本篇教程增加了 4 个文件。

项目结构
src/
├── app/
    ├── api/
        └── auth/
             └── […nextauth]/
                   └── route.ts
    ├── client/
         └── page.tsx
    └── ui/
         └── update_button.tsx
└── auth_wrapper.tsx

1. 服务器端渲染 & 客户端渲染

在继续之前,我们简单讨论一下 Next.js 14 中服务器端渲染(SSR)和客户端渲染(CSR)的区别。网上可以找到详细的比较,所以这里我只解释本教程所需的概念。

Next.js 14 默认使用服务器端渲染来显示网页组件,在组件代码顶部添加 "use client" 行可以让 Next.js 14 使用客户端渲染来显示组件。

在服务器端代码中添加 console.log("message") 会在运行 Next 应用的终端中显示消息,而在客户端代码中添加则会在浏览器控制台显示消息(浏览器 -> 开发者工具 -> 控制台)。

现在我们在 src/app/page.tsx 中添加 console.log,并编写 src/app/client/page.tsx 的代码:

src/app/page.tsx
export default async function Home() {
  console.log('Server Side Rendering') // 添加console.log 
  return (
    ...
  )
}
src/app/client/page.tsx
"use client"

export default function Page() {
  console.log('Client Side Rendering')
  return (
    <div>
      <h1>Client Side Rendeing Page</h1>
      <h2>Unavailable without auth</h2>
    </div>
  )
}

2. Session(会话)

之所以讨论 SSR 和 CSR,是因为 session 是的调用方法取决于代码是在服务器端还是客户端运行。

3. 在服务器端调用session: await auth()

src/auth.ts
// 添加了 { handlers: { GET, POST }, auth, update }
export const { handlers: { GET, POST }, auth, signIn, signOut, update } = NextAuth({
  ...authConfig,

  ...
})
src/app/page.tsx
mport { auth, signOut } from "@/auth" // 添加 { auth }

export default async function Home() { // 现在是'async function'
  console.log('Server Side Rendering')
  const session = await auth() // 调用session
  console.log(session); // console log读取session
  return (
    ...
  )
}

4. 在客户端调用session: useSession()

要在客户端调用 session,我们可以使用 useSession() 钩子。要使用 useSession() 钩子,必须先完成几件事。我们必须设置一个内部 API 并用SessionProvider 包装应用。

src/app/api/auth/[...nextauth]/route.ts
export { GET, POST } from "@/auth"
export const runtime = "edge"
src/app/auth_wrapper.tsx
"use client"; 
// SessionProvider必须与客户端渲染一起使用
// 因此我们创建一个单独的客户端组件来运行AuthWrapper
import { SessionProvider } from "next-auth/react";

type Props = {
  children:React.ReactNode;
}

export default function AuthWrapper({ children }: Props) {
  return <SessionProvider>{children}</SessionProvider>;
}
src/app/layout.tsx
import type { Metadata } from 'next'
import { Inter } from 'next/font/google'
import './globals.css'
import AuthWrapper from '../auth_wrapper'

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">
      <body className={inter.className}>
        <AuthWrapper> {/* 用AuthWrapper包装整个应用 */}
          {children}
        </AuthWrapper>
      </body>
    </html>
  )
}

layout.tsx 文件是一个特殊文件,它作为 page.tsx 组件的父组件。现在让我们编辑 src/app/client/page.tsx

src/app/client/page.tsx
"use client"

import { useSession } from "next-auth/react" // 添加import

export default function Page() {
  console.log('Client Side Rendering')
  const { data: session, update } = useSession() // useSession()
  console.log(session); // console.log
  return (
    <div>
      <h1>Client Side Rendeing Page</h1>
      <h2>Unavailable without auth</h2>
    </div>
  )
}

5. Session更新

在 Next Auth v5 发布之前,更新 session 只能在客户端完成。有了新的 v5,session 更新也可以在服务器端完成。我们首先编辑 src/auth.ts 文件,在callbacks -> jwt 中添加了一个代码块。

src/auth.ts
import NextAuth from 'next-auth';
import { authConfig } from './auth.config';
import Credentials from 'next-auth/providers/credentials';
import { User } from '@/lib/definitions';

export const { handlers: { GET, POST }, auth, signIn, signOut, update } = NextAuth({
  ...authConfig,
  providers: [
    Credentials({
      async authorize(credentials) {
        if (credentials.id && credentials.password) {
          // 在这里添加你的后端代码
          // let loginRes = await backendLogin(credentials.id, credentials.password)
          let loginRes = {
            success : true,
            data : {
              user: {
                ID: "john_doe",
                NAME: "John Doe",
                EMAIL: "email@email.email",
              },
            }
          }
          // 登录失败
          if (!loginRes.success) return null;
          // 登录成功
          const user = {
            id: loginRes.data.user.ID ?? '',
            name: loginRes.data.user.NAME ?? '',
            email: loginRes.data.user.EMAIL ?? '',
          } as User;
          return user;
        }
        return null;
      },
    })
  ],
  callbacks: {
    async session({ session, token, user }) {
      session.user = token.user as User
      return session;
    },
    async jwt({ token, user, trigger, session }) {
      if (user) {
        token.user = user;
      }
      // ***************************************************************
      // 添加的代码
      if (trigger === "update" && session) {
        token = {...token, user : session}
        return token;
      };
       // **************************************************************
      return token;
    },
  },
});

5.1 客户端Session更新

让我们先处理客户端代码,编辑 src/app/client/page.tsx 中的代码:

src/app/client/page.tsx
"use client"

import { User } from "@/lib/definitions"
import { useSession } from "next-auth/react"
import { useEffect, useState } from "react"

export default function Page() {
  const { data: session, update } = useSession()
  // useState和useEffect用于仅在检索到session后
  // 渲染UI
  const [user, setUser] = useState<User>({} as User)
  useEffect(() => {
    if(session && session.user) 
      {
        setUser(session.user as User)
        console.log(session.user)
      }
  }, [session])

  return (
    session && // UI在检索到session后渲染
    <div>
      <h1>Client Side Rendeing Page</h1>
      <h2>Unavailable without auth</h2>
      <br />
      <button onClick={ () => {
      // 使用从useSession钩子调用的update()
        update({...user, name: 'Client-Man'});
      }}>
        Client Side Update
      </button>
    </div>
  )
}

访问 http://localhost:3000/client ,打开浏览器控制台,然后点击 "Client Side Update" 按钮。控制台将打印更新后的 session 用户数据。

5.2 服务器Session更新

在服务器端更新 session 有两种方式。第一种方式是在服务器组件中调用客户端组件来运行客户端 Session 更新,另一种方式是利用 NextAuth v5 的新特性;在服务器组件中使用从 @auth 导入的 update 方法。

5.2.1 服务器组件中的客户端组件

首先,我们在 src/ui/update_button.tsx 中创建一个单独的客户端组件文件。

src/ui/update_button.tsx
"use client"
import { useSession } from "next-auth/react"

export default function UpdateButton({newName} : {newName: String}) {
  const { data: session, update } = useSession()
  return (
    <button onClick={() => {
        update({...session!.user, name: newName});
      }}>
        Client Side Update
    </button>
  )
}

编辑 src/app/page.tsx 文件

src/app/page.tsx
import { auth, signOut } from "@/auth"
import UpdateButton from "@/ui/update_button";

export default async function Home() {
  console.log('Server Side Rendering')
  const session = await auth() // 调用session
  console.log(session); // console log读取session
  return (
    <div>
      <h1>Home Page</h1>
      <h2>Unavailable without auth</h2>
      <form
        action={async () => {
          'use server';
          await signOut();
        }}
        >
        <button>
            Log Out
        </button>
        <br /> 
        <UpdateButton newName={'Server-Man'} />
      </form>
    </div>
  )
}

访问 http://localhost:3000/ ,然后点击'Client Component Update'按钮。刷新页面或转到不同的路由(http://localhost:3000/client)。

5.2.2 在服务器组件中使用从@auth导入的update

编辑 src/app/page.tsx 文件:

src/app/page.tsx
import { auth, signOut, update } from "@/auth"
import UpdateButton from "@/ui/update_button";

export default async function Home() {
  console.log('Server Side Rendering')
  const session = await auth() // 调用session
  const user = session!.user;
  console.log(session); // console log读取session
  return (
    <div>
      <h1>Home Page</h1>
      <h2>Unavailable without auth</h2>
      <form
        action={async () => {
          'use server';
          await signOut();
        }}
        >
        <button>
            Log Out
        </button>
        <br /> 
        <UpdateButton newName={'Server-Man'} />
      </form>
      <br />
      <form
        action={async () => {
          'use server';
          await update({...user, name: 'Serverserver-man'});
        }}
        >
        <button>
          Server Side Update
        </button>
      </form>
    </div>
  )
}

访问 http://localhost:3000/ ,然后点击 Server Side Update 按钮。刷新页面或转到不同的路由(http://localhost:3000/client)。

我们可以检查 session 中的 user.name 已成功更新为'Serverserver-man'

我注意到这个新的 update 功能目前似乎有点不稳定。因此,在 NextAuth v5 正式发布之前,使用客户端组件更新方法将是一个更稳定的选择。