#Authentication in `layout.tsx` & `page.tsx`

50 messages · Page 1 of 1 (latest)

steep slate
#

How are you guys implementing auth in the app router? I want to const session = await getSession() in both my layout.tsx and multiple page.tsx files, and I understand the underlying fetches to my DB would be deduped (but not cached). However, from my testing, I see that when I navigate from route-a/page.tsx to route-b/page.tsx, it fetches to my DB again, as expected. However, the session in route-b/page.tsx is now no longer guaranteed to be the same as the session in layout.tsx, which kept its value from the previous navigation. This could lead to my layout showing a logged in user but my page showing a user is not logged in.

What I'm used to from SvelteKit is that you have a middleware hook that populates event.locals which is recreated for every request, and can be used in both layouts and pages. I don't think Next 13 Middleware allows a similar thing.

How are you solving this? (Note that I'm not using and don't want to use next-auth.)

spice swallow
#

Have you tried giving your fetch a tag so that you can invalidate it on route.ts?

steep slate
#

Yes, but that's not exactly what I want, because I want the simple act of navigating to invalidate the session in the layout, without having to do some programmatic check. To illustrate with an example:

A user has logged in a week ago, and is now using the app on Route A with an almost expired session. So, while they are using Route A, their session token expires. When they navigate to Route B, they should no longer be able to view data that requires logging in. This works fine for the getSession() in Route B, but the getSession() in the layout did not change, so a header in the layout might still show the username and email of a logged in user.

I don't see where I could call revalidateTag() in this process.

spice swallow
#

Ah i see

#

does your fetch use the url outside of your nextjs server?

steep slate
#

What do you mean?

spice swallow
steep slate
#

Ah, I fetch to a Turso database, so https://<database>.turso.io.

spice swallow
#

Ah i see. you'r right, revalidateTag() culdn't be used

steep slate
spice swallow
#

ah interesting

steep slate
#

It's not optimal, as I really only need the data fetching to rerun, not the whole layout including its whole DOM, but it might be a workaround. But it might work, I'll test it!

#

Well, that didn't work at all.

#

It seems like it only recreates the DOM, and doesn't fetch again on navigation?

#

That is... not what I expected.

spice swallow
steep slate
#

Yeah, they're not the trick unfortunately.

thorny plank
#

@steep slate did you find something similar to event.locals in nextjs?

steep slate
stoic tangle
#

I'm working on the same thing for parallel routes. When I first navigate it works correctly, but on a refresh it doesn't. I'm using layout.tsx and have also tried the logic in page.tsx as well as template.tsx

#

import { getSession } from '@/app/supabase-server';

export default async function DashboardPage(props: {
children: React.ReactNode;
conditionA: React.ReactNode;
conditionB: React.ReactNode;
}) {
const session = await getSession();
console.log('this is the console log in page' + session)
return (
<section lang="en">
{session?.user.user_metadata.role === 'conditionA'
? props.conditionA
: props.conditionB}
</section>
);
}

#

On the first route to '/dashboard' layout.tsx operates as intended and props.conditionA is shown. On a refresh, props.conditionB is shown

stoic tangle
#

I had to remove: export const dynamic = 'force-static';

#

and now the routes are working as expected, but I have a separate issue with next canary and cookies

#

getting this error running npm run build: Error: DynamicServerError: Dynamic server usage: cookies

barren tapir
#

also, what is the getSession function

#

does it run a db query each time?

stoic tangle
#

This is getSession function:
export async function getSession() {
const supabase = createServerSupabaseClient();
try {
const {
data: { session }
} = await supabase.auth.getSession();
return session;
} catch (error) {
console.error('Error:', error);
return null;
}
}

#

This is my middleware: import { createMiddlewareClient } from '@supabase/auth-helpers-nextjs'
import { NextResponse } from 'next/server'

import type { NextRequest } from 'next/server'
import type { Database } from '@/types_db'

export async function middleware(req: NextRequest) {
const res = NextResponse.next()
const supabase = createMiddlewareClient<Database>({ req, res })
await supabase.auth.getSession()
return res
}

barren tapir
#

oh wait are y’all working on the same thing?

stoic tangle
#

No I just have the same issue

#

Sorry if it's confusing! I just made another thread

barren tapir
#

ngl tho the times i’ve used middleware it didn’t register every time the session expired or was added

stoic tangle
#

ha that's good to know!

steep slate
barren tapir
#

only way i know of so far

quaint raptor
# steep slate Yes, but that's not exactly what I want, because I want the simple act of naviga...

I'm using Amplify with cognito auth but I conditionally render the navbar in the layout based on the user's authenticated state. So it shows Login when not authenticated and logout when authenticated.

Then for the children, I wrap it with a client-side <Provider> (because that's what amplify offers, you could use server-side). It checks if they're authenticated or not, if not, send to home page. Not sure how your site is structured but maybe this might give some ideas.

export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  
  return (
    <html lang="en">
      <body className={inter.className}>
      {/*conditionally render the nav bar on the client based on authentication*/}
      <NavBar></NavBar>      
        {/*Provider checks to see if user is logged in or not, if not logged in, send to home page*/}
      <Providers>
        {children}
      </Providers>
      {/*footer for every page - terms and conditions, privacy policy*/}
      </body>
    </html>
  )
}```

and providers looks like this
```js
'use client'
import {Authenticator, Theme, ThemeProvider} from '@aws-amplify/ui-react';
import {RequireAuth} from "@/app/RequireAuth";
export function Providers({ children }) {
    return ( 
        <ThemeProvider theme={theme}>
       /*This will assure us that we can use the useAuthenticator hook anywhere in your application without issues.*/
          <Authenticator.Provider>
            {/*if not authenticated, send to home page*/}
              <RequireAuth>
                {children}
              </RequireAuth>
          </Authenticator.Provider>
        </ThemeProvider>
    )
}```
last quest
quaint raptor
# last quest How does your NavBar look? Or rather, how do you determine the authentication st...

Navbar uses 'use-client' because amplify exchanges the oauth authorization code for id/access/refresh tokens from the oauth/token endpoint in cognito from the client, so I needed to do this on the client. Otherwise when the user logs in and gets redirected to the home page, the server didn't have the cookies from the client yet to check if they're logged in so it rendered a non authenticated nav bar.

I determine the authenticated state using amplify which has this call await Auth.currentSession(); which returns the current session if there is one.

Then I simply conditionally render two different navbars based on if they're authenticated or not. You could make it more granular if you have some nav bar links that are common across auth vs not auth and render some on the server vs client, but for mine I needed two different nav bars.

So then the client navbar component is injected into the other server components looking like the image below, but imagine the whole navbar is client component (blue).

So the server can still render all the server components or any static content and then the client side navbar is injected into the page after the client receieves all the html/css/js from the server and renders it themselves.

#

so this way, most of the home page is still rendered on the server for SEO purposes and the parts that require auth state for me are rendered on the client (only because of the client-side cookie issue with amplify).

steep slate
steep slate
honest cradle
#

Are you using the SessionProvider from 'next-auth/react'?

quaint raptor
# steep slate That unfortunately also doesn't work for me, because then you only know whether ...

Yeah you'll have to do it on the server. If you handle your authentication and exchange of tokens etc. all on the server then you should be able to do that though. I was able to check if the user is authenticated from the server, but amplify exchanges tokens on the client. But in your case, if you handle all auth on the server then you can check and exchange tokens all on the server as needed

quaint raptor
barren tapir
#

i would think, since template refreshes each time