使用Authjsv5和next14在客户端组件中获取会话

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

我正在尝试使用带有自定义后端的 authjs@beta 配置我的 next14 应用程序(带有应用程序路由器)。 配置之后,我可以正确地调用服务器组件内的 auth() 方法,并且正确获取会话数据,但是当我尝试在客户端组件内使用 useSession 挂钩时,我得到“{data: null, status: 'unauthenticated'” ,更新:f}"

从一开始我就遇到了很多困难,因为 session.user 的数据不像预定义的数据(电子邮件、姓名等),所以我从令牌中获取数据并通过会话回调

这是我的代码:

“/auth/auth.config.ts”

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

const isTokenExpired = (token: string): Boolean => {
  const base64Url = token.split(".")[1];
  const base64 = base64Url.replace(/-/g, "+").replace(/_/g, "/");
  const decodedPayload = JSON.parse(Buffer.from(base64, "base64").toString("binary"));
  const tokenExpirationDate = new Date(decodedPayload.exp * 1000).getTime();
  const now = Date.now();
  return now >= tokenExpirationDate;
};

export const authConfig = {
  pages: {
    signIn: "/login",
  },
  callbacks: {
    authorized({ auth, request: { nextUrl } }) {
      const isLoggedIn = !!auth?.user;

      if (nextUrl.pathname.startsWith("/login") && isLoggedIn) {
        //NO ESTA ENTRANDO
        // Redirecciona al usuario a la pag root si estan logeados e intentan acceder a la pag de  login.
        return Response.redirect(new URL("/", nextUrl));
      } else if (isLoggedIn) {
        // Revisa si el usuario esta en alguna ruta del root o mayor("/", "/users", "/dashboard", etc.)
        const isOnRootOrAbove = nextUrl.pathname === "/" || nextUrl.pathname.startsWith("/");
        if (isOnRootOrAbove) {
          return true; // Si el Usuario esta en root o una ruta mayor a root y esta logeado, permite el acceso.
        }
      }

      return false; // Por defecto niega el acceso.
    },
    async jwt({ token, user, account, profile, isNewUser }) {
      if (user) {
        return { ...token, ...user };
      }

      if (!!token.accessToken && isTokenExpired(token.accessToken)) {
        console.log("token expired!");
        return null;
      }
      return token;
    },
    async session({ session, token, user }) {
      if (token.user && token.accessToken) {
        //@ts-ignore
        session.user = token.user;
        session.accessToken = token.accessToken;
      }
      return session;
    },
  },
  providers: [],
} satisfies NextAuthConfig;

“/auth/auth.ts”

import Credentials from "next-auth/providers/credentials";
import NextAuth, { AuthError } from "next-auth";
import { authConfig } from "./auth.config";

const credentialsConfig = Credentials({
  async authorize(credentials) {
    const { username, password } = credentials;

    const headers = new Headers();
    headers.append("Content-Type", "application/json");
    const body = JSON.stringify({ username, password });
    try {
      const res = await fetch("http://localhost/api/auth/login", {
        method: "POST",
        headers,
        body,
      });
      const user = await res.json();
      if (user?.status === 0) {
        return null;
      }

      return user;
    } catch (err) {
      throw new AuthError();
    }
  },
});

export const { auth, signIn, signOut } = NextAuth({
  ...authConfig,
  session: { strategy: "jwt" },
  jwt: { maxAge: 60 * 15 },
  providers: [credentialsConfig],
});

“/src/app/providers.tsx”

"use client";
import { SessionProvider } from "next-auth/react";
import { ReactNode } from "react";
const Providers = ({ children }: { children: ReactNode }) => {
  return <SessionProvider>{children}</SessionProvider>;
};

export default Providers;

“/src/app/layout.tsx”

import "@ui/globals.css";
import "@radix-ui/themes/styles.css";
import type { Metadata } from "next";
import { Theme, ThemePanel } from "@radix-ui/themes";
import { LayoutContextProvider, Layout, Navbar } from "@components";
import { DefaultSidebar } from "@layout";
import StoreProvider from "@redux/StoreProvider";
import Providers from "./providers";

export const metadata: Metadata = {
  title: "title",
  description: "description",
};

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <Providers>
      <html lang="es" suppressHydrationWarning>
        <body>
          <Theme appearance="dark" accentColor="red" panelBackground="translucent">
            <StoreProvider>
              <LayoutContextProvider>{children}</LayoutContextProvider>
            </StoreProvider>
            {/* <ThemePanel /> */}
          </Theme>
        </body>
      </html>
    </Providers>
  );
}

“/src/page/(home)/prueba/page.tsx”

"use client";
import { Header } from "@components";
import { useSession } from "next-auth/react";

export default function Prueba() {
  const session = useSession();
  console.log(session);
  return <Header>Prueba</Header>;
}

我的行动: “/src/app/lib/actions.ts”

"use server";
import { z } from "zod";
import { revalidatePath } from "next/cache";
import { redirect } from "next/navigation";
import { auth, signIn, signOut } from "@auth/auth";
import { AuthError } from "next-auth";

const SignInFormSchema = z.object({
  username: z.string().min(4, {
    message: "Username must be at least 4 characters long.",
  }),
  password: z.string().min(1, {
    message: "Password must not be empty",
  }),
});

export type State = {
  errors?: {
    username?: string[];
    password?: string[];
    auth?: string[];
  };
  message?: string | null;
};

export async function authenticate(
  prevState: State,
  formData: FormData
): Promise<State | undefined> {
  const validatedFields = SignInFormSchema.safeParse({
    username: formData.get("username"),
    password: formData.get("password"),
  });
  if (!validatedFields.success) {
    return {
      errors: validatedFields.error.flatten().fieldErrors,
      message: "Campos incompletos. no puede inicial sesion",
    };
  }
  try {
    await signIn("credentials", formData);
  } catch (error) {
    if (error instanceof AuthError) {
      switch (error.type) {
        case "CredentialsSignin":
          return { message: "Invalid credentials." };
        default:
          return {
            message: "Something went wrong.",
          };
      }
    }
    revalidatePath("/");
    redirect("/");
  }
}

export async function logout(): Promise<void> {
  await signOut();
}

我尝试设置 SessionProvider,但我仍然得到一个没有会话的对象。

reactjs next-auth next.js14
1个回答
0
投票

我遇到了类似的问题,你可以尝试以下步骤

//Providers
//1.pass missing `session` prop to "/src/app/providers.tsx".In order to 
get the session in the client component,we can recieve the prop from the 
server component.
"use client";
import { SessionProvider } from "next-auth/react";
import { ReactNode } from "react";
// import Session type from 'next- auth'
import { Session } from "next-auth"; 
const Providers = ({ children,session }: { children: 
ReactNode,session:Session }) => {
// pass the session prop to SessionProvider
return <SessionProvider session={session}>{children}</SessionProvider>; 
};
export default Providers;
 
//RootLayout
//2. pass the session down to the provider from the root layout(must be 
server component)
export default async function RootLayout({children}:{children:ReactNode}){
     const session = await auth()
     <Providers session={session}>{children}</Provider>
}

理论上这个解决方案应该有效,但不幸的是它不起作用。我尝试以下适用于我的情况:

export default async function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
const session = await auth()

return (
<SessionProvider session={session}>
  <html lang="en">
    <body>
      <div>
        {children}
      </div>
    </body>
  </html>
</SessionProvider>
);}

虽然它的行为很奇怪,但会话意外地从非空转换为空,这对我来说真的毫无意义。需要帮助! [https://i.sstatic.net/lh1RIA9F.png]

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