Reputation: 97
I am trying to configure my next14 app(with app router) with authjs@beta with a custom backend. After configuring it i am correctly able to call the auth() method inside server component and i correctly getm my session data, but when i try to use the useSession hook inside client components i get "{data: null, status: 'unauthenticated', update: ƒ}"
Since the begining i had a lot of dificulties because the data of the session.user is not like the pre-defined data (email, name, etc) so i am getting the data from the token and passing it to the user via the session callback
here is my code:
"/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>;
}
my actions: "/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();
}
I tried setting up the SessionProvider but I still get an object without the session.
Upvotes: 2
Views: 625
Reputation: 1
I ran into similar problem,you can try following steps
//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>
}
Theoretically this solution should work,but unfortunately it doesn't.I try the following which works for my case:
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>
);}
Though it behave pretty weird,the session transitions unexpectedly from non-null to null,which really makes no sense to me.Need Help! [https://i.sstatic.net/lh1RIA9F.png]
Upvotes: 0