-
-
Notifications
You must be signed in to change notification settings - Fork 3.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
useSession only getting the session after manually reloading the page #9504
Comments
This comment was marked as off-topic.
This comment was marked as off-topic.
@getr62 Were you able to get around this? |
@vinayavodah unfortunately not yet |
This comment was marked as off-topic.
This comment was marked as off-topic.
I pushed the new branch 'common-reload' to github which solved the problem, at least for now. In the readme is a short description how I implemented this work around. |
Yup, I did the same way, but it still needs to hard reload to refresh the session which is stored in the Session Context. The update function from useSession won't work as expected. |
Hey, Yea that's strange that the useSession hook is not getting the session after you sign in, but what you can do to avoid that custom loader is the following: Since your Header component is a server component you can turn it into an async function and you can get the session directly in the component by awaiting the auth function from ./src/components/header.tsx import Link from "next/link";
import { Suspense } from "react";
import {
Navbar,
NavbarBrand,
NavbarContent,
NavbarItem,
} from "@nextui-org/react";
import HeaderAuth from "@/components/header-auth";
import SearchInput from "@/components/search-input";
import dynamic from "next/dynamic";
import { auth } from "@/lib/auth";
const DynamicHeaderAuth = dynamic(() => import("./header-auth"), {
ssr: false,
});
export default async function Header() {
const session = await auth();
return (
<Navbar className="shadow mb-6">
<NavbarBrand>
<Link href="/" className="font-bold">
Discuss
</Link>
</NavbarBrand>
<NavbarContent justify="center">
<NavbarItem>
<Suspense>
<SearchInput />
</Suspense>
</NavbarItem>
</NavbarContent>
<NavbarContent justify="end">
{/* <HeaderAuth /> */}
<DynamicHeaderAuth session={session} />
</NavbarContent>
</Navbar>
);
} and in the HeaderAuth you can do this ./src/components/header-auth.tsx "use client";
import Link from "next/link";
import {
Chip,
NavbarItem,
Button,
Popover,
PopoverTrigger,
PopoverContent,
} from "@nextui-org/react";
import { Icon } from "react-icons-kit";
import { pacman } from "react-icons-kit/icomoon/pacman";
import { useSession } from "next-auth/react";
import * as actions from "@/actions";
import { Session } from "next-auth";
import { useEffect } from "react";
export default function HeaderAuth({ session }: { session: Session | null }) {
if (session?.user) {
console.log("session 2 from useSession in header-auth: ", session);
return (
<Popover placement="left">
<PopoverTrigger>
<Chip
className="cursor-pointer"
startContent={<Icon icon={pacman} />}
variant="faded"
color="default"
>
{session.user.name}
</Chip>
</PopoverTrigger>
<PopoverContent>
<div className="p-4">
<form action={actions.signOut}>
<Button type="submit">Sign Out</Button>
</form>
</div>
</PopoverContent>
</Popover>
);
}
return (
<>
<NavbarItem>
<Link href={"/sign-in"}>Sign In</Link>
</NavbarItem>
<NavbarItem>
<Link href={"/sign-up"}>Sign Up</Link>
</NavbarItem>
</>
);
} and you should be good to go as far as your header goes. as a side note make sure to always pass the session to SessionProvider in your layout like this: ./page/layout.tsx import type { Metadata } from "next";
import { Inter } from "next/font/google";
import "./globals.css";
import Providers from "@/app/providers";
import Header from "@/components/header";
import { auth } from "@/lib/auth";
const inter = Inter({ subsets: ["latin"] });
export const metadata: Metadata = {
title: "Discuss",
description: "",
};
export default async function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
const session = await auth();
return (
<html lang="en">
<body className={inter.className}>
<div className="container mx-auto px-4 max-w-6xl">
<Providers session={session}>
<Header />
{children}
</Providers>
</div>
</body>
</html>
);
} and in your Providers you can do this ./app/providers.tsx "use client";
import { NextUIProvider } from "@nextui-org/react";
import { Session } from "next-auth";
import { SessionProvider } from "next-auth/react";
interface ProvidersProps {
children: React.ReactNode;
session?: Session | null;
}
export default function Providers({ children, session }: ProvidersProps) {
return (
<SessionProvider session={session}>
<NextUIProvider>{children}</NextUIProvider>
</SessionProvider>
);
} I haven't had this issue, but if I find something out I will keep you posted. Hope this helps for now. You can also refer to the v5 upgrade guide. Keep in mind that it is still in beta and the API will be changing. Take care! |
hi, @bocarw121 Thanks for your help. for my application, I heavily use the useSession hook everywhere, It is not easy to pass the session from sever everywhere. The update function https://next-auth.js.org/getting-started/client#updating-the-session only works when the session is authenticated. any possible to refresh the session by triggering useSession.refresh or something |
hi, @luckykenlin Yea, there is no refresh or anything like that but i found something else. There is the getSession helper that calls /api/auth/session to retrieve the current active session. You can call the getSession helper inside of a hook and return the session and status and use the hook in your client components. ./hooks/useCurrentSession import { Session } from "next-auth";
import { getSession } from "next-auth/react";
import { usePathname } from "next/navigation";
import { useState, useEffect, useCallback } from "react";
// This hook doesn't rely on the session provider
export const useCurrentSession = () => {
const [session, setSession] = useState<Session | null>(null);
const [status, setStatus] = useState<string>("unauthenticated");
const pathName = usePathname();
const retrieveSession = useCallback(async () => {
try {
setStatus("loading");
const sessionData = await getSession();
if (sessionData) {
setSession(sessionData);
setStatus("authenticated");
return;
}
setStatus("unauthenticated");
} catch (error) {
setStatus("unauthenticated");
setSession(null);
}
}, []);
useEffect(() => {
retrieveSession();
// use the pathname to force a re-render when the user navigates to a new page
}, [retrieveSession, pathName]);
return { session, status };
}; ./components/MyComponent "use client";
import { useCurrentSession } from "@/hooks/useCurrentSession";
type MyComponentProps = {};
export const MyComponent = (props: MyComponentProps) => {
const { session, status } = useCurrentSession();
if (status === "loading") {
return <h1>Loading...</h1>;
}
if (status === "unauthenticated") {
return (
<>
<h1>Unauthenticated</h1>
<p>Please sign in to continue.</p>
</>
);
}
return <h1>Welcome {session?.user?.name}</h1>;
}; You can also tweak the hook to fit your needs, hope this helps. |
The problem lies in your code itself. To use the useSession hook, it must know if the session is available or not. So when declaring the session provider context in the layout, the session has to be fetched in the layout and passed on to the sessionprovider to be able to make use of useSession client hook. Without passing the session, the hook doesn't know if the session is available or not. The fix is to provide the session through props right in your provider as also provided by @bocarw121 : ./app/providers.tsx
"use client";
import { NextUIProvider } from "@nextui-org/react";
import { Session } from "next-auth";
import { SessionProvider } from "next-auth/react";
interface ProvidersProps {
children: React.ReactNode;
session?: Session | null;
}
export default function Providers({ children, session }: ProvidersProps) {
return (
<SessionProvider session={session}>
<NextUIProvider>{children}</NextUIProvider>
</SessionProvider>
);
} And in your layout.tsx: export default async function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
const session = await getServerSession(
authOptions
);
return <Providers session={session}>{children}</Providers> |
Hi @bocarw121, thank you for your in depth considerations. The downside of this approach is that then the RootLayout which contains the header becomes a dynamic component, that means all pages become also dynamic pages. When running the build process of this solution we get the following output: In the end that means we loose all the advantages of the more efficient static rendering of Next.js. On my github project I pushed the new branch 'dynamic-header', which uses this approach. Actually it was a previous stage in the development of this project. When you look at the code of the mentioned branch there isn't yet the header-auth.tsx file but the whole code is just in the header.tsx. The code looks like this: // other imports . . .
import { auth } from '@/lib/auth';
export default async function Header() {
const session = await auth();
let authContent: React.ReactNode;
if (session?.user) {
authContent = (
<Popover placement='left'>
<PopoverTrigger>
<Chip
className='cursor-pointer'
startContent={<Icon icon={pacman} />}
variant='faded'
color='default'
>
{session.user.name}
</Chip>
</PopoverTrigger>
<PopoverContent>
<div className='p-4'>
<form action={actions.signOut}>
<Button type='submit'>Sign Out</Button>
</form>
</div>
</PopoverContent>
</Popover>
);
} else {
authContent = (
<>
<NavbarItem>
<Link href={'/sign-in'}>Sign In</Link>
</NavbarItem>
<NavbarItem>
<Link href={'/sign-up'}>Sign Up</Link>
</NavbarItem>
</>
);
}
return (
<Navbar className='shadow mb-6'>
<NavbarBrand>
<Link href='/' className='font-bold'>
Discuss
</Link>
</NavbarBrand>
<NavbarContent justify='center'>
<NavbarItem>
<Suspense>
<SearchInput />
</Suspense>
</NavbarItem>
</NavbarContent>
<NavbarContent justify='end'>{authContent}</NavbarContent>
</Navbar>
);
} In my initial post I forgot to mention, that the original project is taken from a course at Udemy.com which I took there. I only wrote this in the readme on github. I also forgot to mention that there only an OAuth-Provider was implemented and because of that the session strategy 'database' including the PrismaAdapter was used. The interesting fact is that in the original project taken from Udemy everything is working fine with the useSession hook. With regard to the authentication process I just took away the original auth provider as well as the in this case not applicable PrismaAdapter and put in the CredentialProvider. |
My pleasure, happy to see you found a solution! |
No sorry, that is no solution for me at all. The 'solution' in my previous post was just a step while developing this demo project. I would rather switch to another authentication package like https://lucia-auth.com or use a simple jsonwebtoken alone and and have to write the whole boilerplate code for this than loosing the advantages of static rendered pages in Next.js. I know my English is bad because it is a foreign language for me. But what I wanted to say in my previous post was that when using const session = await auth(); in the header.tsx file the build process leads to the following result: Whereas using const session = useSession(); in a component marked with the 'use client' directive would lead to the folloing result: So the logic to check the authentication state was cut out of the header.tsx file and put into the header-auth.tsx file. The teacher in the Udemy course I mentioned used only the GithubProvider together with the PrismaAdapter and so the session strategy applied is 'database'. In such cases the call of the useSession hook just works fine and everything is ok. As I mentioned in my initial post I will not be able to use Github, Google or all these other providers. The only option I am allowed to use in my app which I have to make is authentication with username and password because the app will not be reachable over a public website in the internet. It can only be accessed in a private network. Therefore I took away the GithubProvider and instead put in the CredentialProvider which switches the session strategy automatically to 'jwt'. But apparently auth.js has a bug (or feature?), that in this case the useSession hook can not retrieve the session. |
Ahh okay gotcha, your english is good no worries. I was thinking maybe you can try this and it will lead to the build results your expecting. In your sign-in action instead of using the ./actions/sign-in "use server";
import type { Profile } from "@prisma/client";
import { z } from "zod";
import { db } from "@/db";
import { redirect } from "next/navigation";
import paths from "@/lib/paths";
import * as auth from "@/lib/auth";
import { compare } from "bcrypt";
const signInSchema = z.object({
username: z.string().min(3),
password: z.string().min(7),
});
interface SignInFormState {
errors: {
username?: string[];
password?: string[];
_form?: string[];
};
// Add this
success?: boolean;
}
export async function signIn(
formState: SignInFormState,
formData: FormData
): Promise<SignInFormState> {
const result = signInSchema.safeParse({
username: formData.get("username"),
password: formData.get("password"),
});
console.log("result signInSchema safeParse: ", result);
if (!result.success) {
return {
errors: result.error.flatten().fieldErrors,
};
}
const user = await db.user.findUnique({
where: {
name: result.data.username,
},
});
if (!user) {
return {
errors: {
_form: ["Wrong credentials"],
},
};
}
const passwordsMatch = await compare(result.data.password, user.password);
if (!passwordsMatch) {
return {
errors: {
_form: ["Wrong credentials"],
},
};
}
console.log("user found in sign-in action: ", user);
let profile: Profile;
try {
await auth.signIn("credentials", {
redirect: false,
username: result.data.username,
password: result.data.password,
});
profile = await db.profile.upsert({
where: {
userId: user.id,
},
update: {
lastLogin: new Date().toLocaleString(),
},
create: {
userId: user.id,
lastLogin: new Date().toLocaleString(),
},
});
} catch (err: unknown) {
if (err instanceof Error) {
console.log("error in auth.signIn action: ", err);
return {
errors: {
_form: [err.message],
},
};
} else {
return {
errors: {
_form: ["Something went wrong"],
},
};
}
}
return {
errors: {},
// add this
success: true,
};
}
and update the sign in form like this ./user/sign-in-form.tsx import { useEffect } from "react";
const [formState, action] = useFormState(actions.signIn, {
errors: {},
success: false,
});
useEffect(() => {
if (formState.success) {
// using replace so the user can't go back to the login page if they
// press the back button
window.location.replace("/");
}
}, [formState.success]); As a side note the SessionProvider actually doesn't need the session as it retrieves it under the hood if the session prop is not passed, but if you did want to pass it and avoid all your pages being dynamic you can do this in your providers.tsx "use client";
import { NextUIProvider } from "@nextui-org/react";
import { Session } from "next-auth";
import { SessionProvider, getSession } from "next-auth/react";
import { useCallback, useEffect, useState } from "react";
interface ProvidersProps {
children: React.ReactNode;
}
export default function Providers({ children }: ProvidersProps) {
const [session, setSession] = useState<Session | null>(null);
const fetchSession = useCallback(async () => {
const session = await getSession();
setSession(session);
}, []);
useEffect(() => {
fetchSession();
}, [fetchSession]);
return (
<SessionProvider session={session}>
<NextUIProvider>{children}</NextUIProvider>
</SessionProvider>
);
} Hope this helps! |
Thank you so much @bocarw121 , you already helped me a lot!!! I took the useCurrentSession hook from your post a few days ago and put it without further changes into my project: import { Session } from 'next-auth';
import { getSession } from 'next-auth/react';
import { usePathname } from 'next/navigation';
import { useState, useEffect, useCallback } from 'react';
// This hook doesn't rely on the session provider
export const useCurrentSession = () => {
const [session, setSession] = useState<Session | null>(null);
const [status, setStatus] = useState<string>('unauthenticated');
const pathName = usePathname();
const retrieveSession = useCallback(async () => {
try {
setStatus('loading');
const sessionData = await getSession();
if (sessionData) {
setSession(sessionData);
setStatus('authenticated');
return;
}
setStatus('unauthenticated');
} catch (error) {
setStatus('unauthenticated');
setSession(null);
}
}, []);
useEffect(() => {
retrieveSession();
// use the pathname to force a re-render when the user navigates to a new page
}, [retrieveSession, pathName]);
return { session, status };
}; Then I changed the header-auth.tsx: 'use client';
import { useCurrentSession } from '@/hooks/useGetSession';
//other imports . . .
export default function HeaderAuth() {
const { session, status } = useCurrentSession();
let authContent: React.ReactNode;
if (status === 'loading') {
authContent = null;
} else if (session && status === 'authenticated') {
authContent = (
<Popover placement='left'>
<PopoverTrigger>
<Chip
className='cursor-pointer'
startContent={<Icon icon={pacman} />}
variant='faded'
color='default'
>
{session!.user!.name}
</Chip>
</PopoverTrigger>
<PopoverContent>
<div className='p-4'>
<form action={actions.signOut}>
<Button type='submit'>Sign Out</Button>
</form>
</div>
</PopoverContent>
</Popover>
);
} else {
authContent = (
<>
<NavbarItem>
<Link href={'/sign-in'}>Sign In</Link>
</NavbarItem>
<NavbarItem>
<Link href={'/sign-up'}>Sign Up</Link>
</NavbarItem>
</>
);
}
return authContent;
} Now the authentication works finally! I myself already experimented with the getSession function but I did not get it right with the combination of useState, useEffect and useCallback. I always run into an endless re-render loop. If I were a girl I would say you are my hero ;) but so I will just say you are terrific awesome! A small downer remains, because when navigating between pages there is a short flickering of the user button on the right side of the navigation bar: flickering.navbar.when.switching.pages.mp4Probably I need to write my own context provider for the session which the useCurrentSession returns to wrap the RootLayout within so all pages can immediately take the already available session? But nevertheless I now have a working solution. Thank you so much again! You should consider teaching your skills on sites like Udemy.com or do you have a Youtube channel? I pushed the new branch "using-getSession" to my demo project on github where this working solution from you is implemented, so if anybody is interested can take a look there. Now I will try to implement the suggestions of your last post :) |
It's my pleasure! Happy to hear things are going the right away great job! Thanks a lot I appreciate it 😄! I don't have any courses on Udemy or a YouTube but I will definitely consider doing that. As far as the flash it's because the ./hooks/useCurrentSession // This hook doesn't rely on the session provider
export const useCurrentSession = () => {
const [session, setSession] = useState<Session | null>(null);
// Changed the default status to loading
const [status, setStatus] = useState<string>("loading");
const pathName = usePathname();
const retrieveSession = useCallback(async () => {
try {
const sessionData = await getSession();
if (sessionData) {
setSession(sessionData);
setStatus("authenticated");
return;
}
setStatus("unauthenticated");
} catch (error) {
setStatus("unauthenticated");
setSession(null);
}
}, []);
useEffect(() => {
// We only want to retrieve the session when there is no session
if (!session) {
retrieveSession();
}
// use the pathname to force a re-render when the user navigates to a new page
}, [retrieveSession, session, pathName]);
return { session, status };
}; Also, if you implement the changes I gave you in the sign-in action and sign-in-form, you'll be able able to use the useSession hook directly. Let me know if it helps. |
Now I say it, @bocarw121 , you are not only a hero ... you are a superhero! The flashing issue is solved and everything runs smoothly! Tomorrow I will implement the changes in the sign-in action and sign-in-form, because my cats demand my immediate attention. The whole day I neglected them besides feeding them (otherwise they would have eaten me) and me was just fixated on fixing my coding problems. I can't express how happy and relieved I am feeling and how grateful to you I am. As soon as I made these changes I will also let you know. |
Sweet! That's awesome to hear! Nice 😆 have a good time with your cats. Yea, definitely keep me posted. |
Hello. The previous discussion was helpful. I'm using the v5 beta, and also running into issues with the server-side
I'm wondering if there's any way to sync the |
Hello has there been any update on this, I am running through the same issue as you guys, my main problem is that supposedly Thanks! |
I had a related problem, where I was expecting useSession() to get the session from the server, but not the server cache. I think maybe the jwt callback documentation should mention that, useSession() does not guarantee the jwt callback will be invoked. Maybe add:
|
I hope I'm not hyjacking this thread but it seems there might be another (easier) way to demonstrate this problem. I'm here from the email provider side. If I click the activation link in the email then it jumps from my email client onto a fresh page and I'm logged in with a valid session on the client and the server. However, I'm looking at verification links being taken by link security checkers, see here. I encrypt the link and send the user to my custom verification page where the link is decoded and put under a Link. When the user clicks the link, it looks like Next does a fetch to get the anchor, rather than getting the page itself. The response is coming back with the location redirect but I don't think (or at least it dosen't look like) the cookie which is also coming back is being remembered as the client is moved onto the location page but the client session has not been updated with the new token. This seems like the same problem. I was wondering if the
|
Problem: User SignIn Via login and password. -> return success to client. but doesn't update the session. Hard Refresh is required Solution: "use server";
import { signIn } from "@/config/next-auth/auth";
export const loginAction =async (input: TLoginSchema) ={
// validate user input
try {
await signIn("credentials", {
email,
password,
redirectTo: DEFAULT_LOGIN_REDIRECT,
});
} catch (error) {
if (error instanceof CredentialsSignin) {
switch (error.code) {
case "custom_error":
return { error: "Please Verify your email" };
// other custom cases you throw from SignIn including null value should be handled
default:
return { error: "Something went wrong!" };
}
}
throw error;
}
} The problem that client is not updating the session is because you need to throw a error in the end. |
I noticed that problem recently too with a simple try {
await signIn('credentials', {
redirectTo: '/',
email,
password,
})
} catch (error) {
// ... Handle authentication errors from the provider
console.log(error)
throw error
} When I log the error, I get this:
The session on the back side with I do not know when happened that change from Next.js which prevent redirection from a vercel/next.js#55586 |
I could fix that problem by replacing // next-auth.d.ts
// Make the getCsrfToken accessible outside of next-auth package
declare module 'next-auth/react' {
function getCsrfToken(): Promise<string>
} // SessionDataWrapper.tsx (replace the current SessionProvider from next-auth/react)
'use client'
import React, { Context, createContext, type PropsWithChildren, useEffect, useMemo, useState } from 'react'
import { usePathname } from 'next/navigation'
import type { Session } from 'next-auth'
import { getCsrfToken } from 'next-auth/react'
/**
* Provider props
*/
type TSessionProviderProps = PropsWithChildren<{
session?: Session | null
}>
/**
* Type of the returned Provider elements with data which contains session data, status that shows the state of the Provider, and update which is the function to update session data
*/
type TUpdateSession = (data?: any) => Promise<Session | null | undefined>
export type TSessionContextValue = { data: Session | null; status: string; update: TUpdateSession }
/**
* React context to keep session through renders
*/
export const SessionContext: Context<TSessionContextValue | undefined> = createContext?.<TSessionContextValue | undefined>(undefined)
export function SessionDataProvider({ session: initialSession = null, children }: TSessionProviderProps) {
const [session, setSession] = useState<Session | null>(initialSession)
const [loading, setLoading] = useState<boolean>(!initialSession)
const pathname: string = usePathname()
useEffect(() => {
const fetchSession = async () => {
if (!initialSession) {
// Retrive data from session callback
const fetchedSessionResponse: Response = await fetch('/api/auth/session')
const fetchedSession: Session | null = await fetchedSessionResponse.json()
setSession(fetchedSession)
setLoading(false)
}
}
fetchSession().finally()
}, [initialSession, pathname])
const sessionData = useMemo(
() => ({
data: session,
status: loading ? 'loading' : session ? 'authenticated' : 'unauthenticated',
async update(data?: any) {
if (loading || !session) return
setLoading(true)
const fetchOptions: RequestInit = {
headers: {
'Content-Type': 'application/json',
},
}
if (data) {
fetchOptions.method = 'POST'
// That is possible to replace getCsrfToken with a fetch to /api/auth/csrf
fetchOptions.body = JSON.stringify({ csrfToken: await getCsrfToken(), data })
}
const fetchedSessionResponse: Response = await fetch('/api/auth/session', fetchOptions)
let fetchedSession: Session | null = null
if (fetchedSessionResponse.ok) {
fetchedSession = await fetchedSessionResponse.json()
setSession(fetchedSession)
setLoading(false)
}
return fetchedSession
},
}),
[loading, session],
)
return <SessionContext.Provider value={sessionData}>{children}</SessionContext.Provider>
} // useSessionData.ts (replace useSession hook)
'use client'
import { useContext } from 'react'
import type { Session } from 'next-auth'
import { SessionContext, type TSessionContextValue } from '@/components/wrapper/SessionDataWrapper'
/**
* Retrieve session data from the SessionContext for client side usage only.
* Content:
* ```
* {
* data: session [Session | null]
* status: 'authenticated' | 'unauthenticated' | 'loading'
* update: (data?: any) => Promise<Session | null | undefined>
* }
* ```
*
* @throws {Error} - If React Context is unavailable in Server Components.
* @throws {Error} - If `useSessionData` is not wrapped in a <SessionDataProvider /> where the error message is shown only in development mode.
*
* @returns {TSessionContextValue} - The session data obtained from the SessionContext.
*/
export function useSessionData(): TSessionContextValue {
if (!SessionContext) {
throw new Error('React Context is unavailable in Server Components')
}
const sessionContent: TSessionContextValue = useContext(SessionContext) || {
data: null,
status: 'unauthenticated',
async update(): Promise<Session | null | undefined> {
return undefined
},
}
if (!sessionContent && process.env.NODE_ENV !== 'production') {
throw new Error('[auth-wrapper-error]: `useSessionData` must be wrapped in a <SessionDataProvider />')
}
return sessionContent
} And inside the root layout: // app/layout.tsx
// In my case, as I use localized routes, everything is under app/[locale]
import React from 'react'
import type { ResolvingViewport } from 'next'
import { SessionDataProvider } from '@/components/wrapper/SessionDataWrapper'
import type { TLocalisation } from '@/schema/Localisation'
import { locales } from '@/schema/constants/Language'
import '@/styles/index.scss'
export async function generateViewport(): ResolvingViewport {
return {
width: 'device-width',
height: 'device-height',
initialScale: 1,
maximumScale: 1,
userScalable: false,
}
}
export async function generateStaticParams() {
return locales.map((language: TLocalisation) => ({ locale: language }))
}
export default async function RootLayout({ children }: React.PropsWithChildren) {
return <SessionDataProvider>{children}</SessionDataProvider>
} |
Hi all, great discussion. I myself have encountered similar problems, namely:
I am currently on NextJS v14.2.3 with next_auth/5.0.0-beta.18 and app routing. The first and second issue seems to be linked to how redirects work in NextJS. try {
await signIn("credentials", { email, password, redirectTo: DEFAULT_LOGIN_REDIRECT});
} catch (error) {
if (isRedirectError(error)) {
throw error;
}
if (error instanceof AuthError) {
switch (error.type) {
case "CredentialsSignin":
return { error: "Invalid email/password!" };
default:
return { error: "Something went wrong while logging in!" };
}
}
throw error;
} As for issue 3, the only workaround that gives the best of both worlds for me currently is to keep the use of SessionProvider as close to the components that require auth, instead of in the root layout. A similar issue was discussed here in the case of Clerk: https://github.com/orgs/clerk/discussions/1764. Hope this helps and do let me know if there are better ways about this! |
Finally, I just added a custom-session-provider to work around import { Session } from 'next-auth'
import { getSession as nextAuthGetSession, useSession } from 'next-auth/react'
import React, { ReactNode, createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react'
type RefetchSession = () => Promise<void>
type CustomSessionContextValue =
| { refetch: RefetchSession; data: Session; status: 'authenticated' }
| {
refetch: RefetchSession
data: null
status: 'unauthenticated' | 'loading'
}
const CustomSessionContext = createContext<CustomSessionContextValue | undefined>(undefined)
interface CustomSessionProviderProps {
children: ReactNode
}
/**
* @description 由于 next-auth 的 useSession 获取是在 signIn 之前,所以 signIn 之后无法通过调用 update 重新获取
* 所以需要自定义一个 useCustomSession 来对 session 进行 hack
* @param param0
* @returns
*/
export const CustomSessionProvider: React.FC<CustomSessionProviderProps> = ({ children }) => {
const { data: _session, status: _status } = useSession()
const [session, setSession] = useState<Session | null>(null)
const [status, setStatus] = useState<'loading' | 'unauthenticated' | 'authenticated'>('loading')
const fetchSession = useCallback(async () => {
setStatus('loading')
const session = await nextAuthGetSession()
setSession(session)
setStatus(session ? 'authenticated' : 'unauthenticated')
}, [])
useEffect(() => {
setSession(_session)
setStatus(_status)
}, [_status, _session])
const value: CustomSessionContextValue = useMemo(() => {
if (session) {
return {
status: 'authenticated',
data: session,
refetch: fetchSession,
}
}
if (status === 'loading') {
return {
status: 'loading',
data: null,
refetch: fetchSession,
}
}
return {
status: 'unauthenticated',
data: null,
refetch: fetchSession,
}
}, [fetchSession, session, status])
return <CustomSessionContext.Provider value={value}>{children}</CustomSessionContext.Provider>
}
export const useCustomSession = (): CustomSessionContextValue => {
const context = useContext(CustomSessionContext)
if (!context) {
throw new Error('useCustomSession must be used within a CustomSessionProvider')
}
return context
} // _app.tsx
const MyApp: AppType<{ session: Session | null }> = ({ Component, pageProps: { session, ...pageProps } }) => {
return (
<SessionProvider session={session}>
<CustomSessionProvider>
<Component {...pageProps} />
</CustomSessionProvider>
</SessionProvider>
)
} |
the problem could be solved by just using signIn function from next-auth/react |
After coming back to NextAuth in 5.0.0-beta.18 ,there's still a synchronization issue between server Like sarowarhosen03 said, we can just switch to the client-side versions. The biggest issue with the client-side versions was the inability to throw custom errors from
|
Any update on the issue ? I noticed everything works fine on next 14.1.4 but anything above that the useSession hook needs a page reload |
Same issue in signIn redirectTo Options. |
2024-07-10.18-34-22.mp4 |
Get session on the server component. I am currently handling it this way. Uncertain if there are any drawbacks. const DashboardLayout = async ({ children }: { children: React.ReactNode }) => {
return (
<div>
<HeaderWrapper> {/* Client Component */}
<Header /> {/* Server Component */}
</HeaderWrapper>
...
</div>
)
} 'use client'
const HeaderWrapper = ({ children }: { children: React.ReactNode }) => {
// useState、 useEffect...
return (
<header
>
{children}
</header>
)
}
export default HeaderWrapper const Header = async () => {
const session = await auth()
// ...
}
export default Header |
Hi all, |
Having the same issue on version export function SessionProvider(props: SessionProviderProps) {
if (!SessionContext) {
throw new Error("React Context is unavailable in Server Components")
}
const { children, basePath, refetchInterval, refetchWhenOffline } = props
if (basePath) __NEXTAUTH.basePath = basePath
/**
* If session was `null`, there was an attempt to fetch it,
* but it failed, but we still treat it as a valid initial value.
*/
const hasInitialSession = props.session !== undefined
/** If session was passed, initialize as already synced */
__NEXTAUTH._lastSync = hasInitialSession ? now() : 0
const [session, setSession] = React.useState(() => {
if (hasInitialSession) __NEXTAUTH._session = props.session
return props.session
})
// no more props.session usage is seen below here
// ... so I tried to workaround this problem by giving // /app/layout.tsx
export default async function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
const session = await auth();
const sessionKey = new Date().valueOf();
return (
<html lang="ko">
<body>
<div className="">
<Providers session={session} sessionKey={sessionKey}>
{children}
</Providers>
</div>
</body>
</html>
);
} // Providers.tsx
'use client';
function Providers({ children, ...props }: Props) {
const { session } = props;
const memoizedSessionKey = useMemo(() => {
console.log('session changed >>> ', session);
return sessionKey;
}, [session]);
return (
<SessionProvider key={memoizedSessionKey} session={session}>
{children}
</SessionProvider>
);
} when the session/key prop changes, it will cause the remount of the any suggestions / missing points on this workaround?? |
i had the same issue, the workaround from @mskwon1 resolved that for me. |
Had the same issue, @mskwon1 fix worked for me as well. |
For me the client side session resets when I login with an oauth provider like github but not when I login with credentials. So there has to be something wrong with my The fix mentioned above works for me too but it seems more like a hack than a solution. This is my solution for now:
|
Had to have the token on client side for use with Apollo, but it wasn't available when using // app/layout.tsx
...
import { SessionProvider } from "next-auth/react";
// set up apollo client for client side of things
import { ApolloWrapper } from "@/lib/apolloWrapper.tsx";
// next-auth config
import { auth } from "@/config/auth";
export default async function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
const session = await auth();
const locale = await getLocale();
const messages = await getMessages();
return (
<html lang={locale}>
<body>
<SessionProvider session={session}>
<ApolloWrapper>
<NextIntlClientProvider messages={messages}>
{children}
</NextIntlClientProvider>
</ApolloWrapper>
</SessionProvider>
</body>
</html>
);
} |
thanks @mskwon1 this works like a charm! The only issue is that the all the routes in that layout becomes dynamic and hence nextjs loses its purpose. Can we do something about it? |
I played around with a few solutions here but this seems to provide the best resolution to the issue. Some solutions forced dynamic rendering to all the routes in the layout. This one worked perfectly fine thanks @AmandineTournay
|
I also have the same issue. |
Why it's not fix by official team yet! It's been a long and almost everyone facing the issue. |
I cannot believe that the only working solution is "do not use authjs SessionProvider, write your own hook" xD |
Hopefully the official team can fix this in the near future I come up with a very simple solution its not the best but it will allow me to keep moving instead of wasting days trying to to figure this out. Here is my simple provisional approach. In my login server action I set to false the redirect so I can handle the redirect in the client side in my form. I revalidate whole cache also, so basically I return a success object {success: "Logged in"}. Then in my form I catch that success object and I perform a window.location.href = "/settings"; this perform a hard reload. It's not as clean as as a router navigation but it allows to populate the SessionProvider correctly. I don't matter to much since is the first navigation to the application everything else works fine apparently event the update method from useSession(). If they fix the issue the you just simply need to revert to: await signIn("credentials", {email,password,redirectTo: [your redirect route]}); // sign-in-form
import { login } from "@/actions/server/login";
const onSubmit = (values: z.infer<typeof LoginSchema>) => {
setError("");
setSuccess("");
startTransition(() => {
login(values)
.then((data) => {
if (data?.error) {
form.reset();
setError(data.error);
}
if (data?.success) {
window.location.href = "/dashboard";
}
})
.catch((error) => {
setError("Something went wrong");
});
});
}; // login action
try {
await signIn("credentials", {
email,
password,
redirect: false,
});
revalidatePath("/");
return { success: "Logged in" };
} catch (error) {
if (error instanceof AuthError) {
switch (error.type) {
case "CredentialsSignin":
return { error: "Invalid email or password" };
default:
return { error: "Something went wrong" };
}
}
console.error("Unexpected error during login:", error);
return { error: "An unexpected error occurred" };
} |
I was facing two issues: sign-in with cookie-free browser the session was not updating, so none info was appearing until a page reload, and after a logout with a user and login with another one the previous user infos was showed. I fixed it with the code below
|
This workaround fixed it by forcefully reloading the session |
Environment
System:
OS: Linux 5.15 Ubuntu 22.04.3 LTS 22.04.3 LTS (Jammy Jellyfish)
CPU: (6) x64 Common KVM processor
Memory: 48.93 GB / 62.79 GB
Container: Yes
Shell: 5.1.16 - /bin/bash
Binaries:
Node: 20.10.0 - ~/.nvm/versions/node/v20.10.0/bin/node
npm: 10.2.3 - ~/.nvm/versions/node/v20.10.0/bin/npm
npmPackages:
@auth/core: 0.18.1 => 0.18.1
next: 14.0.2 => 14.0.2
next-auth: ^5.0.0-beta.4 => 5.0.0-beta.4
react: ^18 => 18.2.0
Browser:
Chrome
Fireox
Reproduction URL
https://github.com/getr62/discuss
Describe the issue
I am using the credential provider, because I have to develop an app which is only available in a local network, maybe later also running on computers even without connection to the internet, so OAuth etc is no option for me.
src/lib/auth.ts
In Next.js I am using the app router with server actions, also for the sign-in functionality.
src/actions/sign-in.tx
The logged in user should be displayed in the header component. The header is part of the root-layout file. To avoid that in the build process every page is treated as a dynamic route, this on the authentication state depending part is put in a client component.
src/components/header
src/components/header-auth
As you can see in the auth.ts file I am heavily logging different stages in the jwt and session. Basically I am able to prep the token with the user id and role as well as all these values into the session with just one big problem. The session is only created, after I manually reload the page. To showcase I uploaded a video where you can see in the browser console that the session state only changes from unauthenticated to authenticated when the page is manually reloaded.
sign-in-short.mp4
Sure, I am by far no experienced developer, but I read in the discussions that some other people have issues too using the useSession hook and not getting the session back from the server.
How to reproduce
I linked a small example project, where I was experimenting with these features like server actions. In there should be everything to run the app with the exception of the .env file which contains these values:
As you can see, nextjs runs on port 3004 which is hard coded in project.json and the database port is set to port 5434, which is also used in the docker-compose.yml
Expected behavior
After signing into the app the authentication state should change automatically from unauthenticated to authenticated.
The text was updated successfully, but these errors were encountered: