middleware icon indicating copy to clipboard operation
middleware copied to clipboard

Getting Auth Session in a server component, Set CallbackURL issue!

Open aifirstd3v opened this issue 10 months ago • 8 comments

I'am developing a simple NextJS app with hono & auth.js.

My Question is

  1. How to make a valid hook function to get the auth session normally in server components. auth.js middlware only supports to get it in client components through useSession hook or session provider?
  2. callbackUrl doesn't work with signIn(NextAuth library works! tho), I've inspected the cookie in the browser. After processing signIn and authHandler, the middleware overwrites the authjs.callback-url cookie for the default callback url. So even if I call setCookie to overwrite the cookie, it doesn't work.

For sure, The Client useSession works like the official document(readme).

import { useQuery } from '@tanstack/react-query'

export const useSession = () => {
  const { data, status } = useQuery({
    queryKey: ['session'],
    queryFn: async () => {
      const res = await fetch('/api/auth/session')
      // console.log('useSession res: ', res)

      return res.json()
    },
    staleTime: 5 * (60 * 1000),
    gcTime: 10 * (60 * 1000),
    refetchOnWindowFocus: true
  })
  return { session: data, status }
}
'use client'
import { useSession } from '@/hooks/use-session'
export default function page() {
  const { status, session } = useSession()
...

But, I'd like to get an auth session from a server component in NextJS. So, I created a hook for this purpose.

export async function checkServerSession() {
  const fetchUrl = process.env.BASE_URL + '/api/auth/session'
  // console.log("fetchUrl: ", fetchUrl)
  const res = await fetch(fetchUrl)
  const result = await res.json() 
  console.log('checkServerSession res: ', result)

  return result
}
import 'server-only'
import { checkServerSession } from '@/hooks/checkServerSession'
import { redirect } from 'next/navigation'

async function layout({ children }: { children: ReactNode }) {
  const session = await checkServerSession()
  

  if (!session) {
    console.log("No Session Found")
    redirect('/signin?callbackUrl=/finance')
  } else {
    console.log('Session Found: ', session)
  }
  ...

However, The session is always Null although I have've signed-in and had an auth session after github(provider) login. I need get a session in server components not client components because of a special purpose(to check the auth status in the server more securely without evaluating a client page component. ) Next-Auth library has a getServerSession() but it seems that hono auth.js middleware doesn't have such a thing.

signIn('GitHub', { redirect: false, callbackUrl: searchParams.get('callbackUrl') || '/' })
const app = new Hono()
app.use(logger(customLogger))

app.use(
'*',
cors({
  origin: (origin) => origin,
  allowHeaders: ['Content-Type'],
  allowMethods: ['*'],
  maxAge: 86400,
  credentials: true
})
)

app.use('*', initAuthConfig(getAuthConfig))
const authMiddleware = async (c: Context, next: Next) => {
console.log('👍Hono authMiddleware!')
// console.log("Context: ", c)
await next()
}
const setCookieMiddleware = async (c: Context, next: Next) => {
console.log('👍Hono setCookieMiddleware!')
setCookie(c, 'authjs.callback-url', 'http://localhost:3000/finance')
await next()
}
app.use('/api/auth/*', authMiddleware, setCookieMiddleware, authHandler())

Any idea would be helpful 👍

aifirstd3v avatar Apr 29 '24 02:04 aifirstd3v

Hi @aifirstd3v

@divyam234 Do you have any idea?

yusukebe avatar May 01 '24 03:05 yusukebe

@aifirstd3v If you are using nextjs you should use next-auth only as both of them are tightly coupled. Your checkServerSession implementation will not work as on server side you need to get session from cookies rather than calling fetch. Also getting auth session requires passing honojs context which is not possible in server components.But you can write simple authChecker in server components also.

import { Auth } from '@auth/core'
import type { Session } from '@auth/core/types'
import type { JWT } from '@auth/core/jwt'
import type { AdapterUser } from '@auth/core/adapters'

type AuthUser = {
  session: Session
  token?: JWT
  user?: AdapterUser
}

function getServerSession(req:Request) {
  const config = initAuthConfig()  // Authjs config
  config.secret ??= process.env.AUTH_SECRET
  config.basePath ??= '/api/auth'
  config.trustHost = true
  const origin = process.env.AUTH_URL ? new URL(process.env.AUTH_URL ).origin : new URL(c.req.url).origin
  const request = new Request(`${origin}${config.basePath}/session`, {
    headers: { cookie: req.headers.get('cookie') ?? '' },
  })

  let authUser: AuthUser = {} as AuthUser

  const response = (await Auth(request, {
    ...config,
    callbacks: {
      ...config.callbacks,
      async session(...args) {
        authUser = args[0]
        const session = (await config.callbacks?.session?.(...args)) ?? args[0].session
        const user = args[0].user ?? args[0].token
        return { user, ...session } satisfies Session
      },
    },
  })) as Response

  const session = (await response.json()) as Session | null

  return session && session.user ? authUser : null
}

You can add this in Nextjs middleware

divyam234 avatar May 01 '24 07:05 divyam234

@divyam234 Thing is how to call the getServerSession(req) in server components with Request parameter? I don't know how to pass the req in nextjs server component..

Also, setting callbackUrl doesn't work. After logging, there is no way return to the callback url that I set.

Well..If I use this auth middleware in react 19 with server components, I think it would be very useful to have such a feature for a server component.

aifirstd3v avatar May 02 '24 16:05 aifirstd3v

@aifirstd3v you dont have to use this in server component use this in nextjs midleware which gives you request object and run this for client routes you want to secure.

divyam234 avatar May 03 '24 11:05 divyam234

@aifirstd3v Of course, it should work for the NextJS middleware because it provides the Request/Response. I just wanted to know how to use such a thing in server components freely :).
I think it would be better to use Next-Auth itselt for NextJS apps like you mentioned ! Thanks anyway for helping me.

aifirstd3v avatar May 05 '24 06:05 aifirstd3v

The original problem is probably related to https://github.com/honojs/middleware/issues/537 (which now includes a fix) - getAuthUser() fails because origin gets set without taking into consideration the X-Forwarded-* headers.

beorn avatar May 26 '24 23:05 beorn

Does anyone manage to get the auth session in server components?

alvalau avatar Jun 08 '24 02:06 alvalau

@alvalau @aifirstd3v

This works for me while using Tanstack Query. Took me a while but so far it seems to work without problems. The original problem is server components dont include headers (why not(!)) when doing a fetch request. Client components -> included by default. Because Tanstack queries run on server (eg. prefetch) and also on the client, when being in a server component just load the next headers and append them to the request.

export const client = hc<AppType>(getBaseUrl(), {
  async fetch(input, requestInit, Env, executionCtx) {
    return fetch(input, {
      ...requestInit,
      ...(typeof window === "undefined"
        ? (await import("next/headers")).headers()
        : {}),
    });
  },
});

cptlchad avatar Sep 19 '24 20:09 cptlchad

@cptlchad

What do you mean append them to the request? In your tanstack queries for useSession? Can you provide some more code please im having trouble with this.

Harimz avatar Nov 01 '24 23:11 Harimz

@alvalau @aifirstd3v

This works for me while using Tanstack Query. Took me a while but so far it seems to work without problems. The original problem is server components dont include headers (why not(!)) when doing a fetch request. Client components -> included by default. Because Tanstack queries run on server (eg. prefetch) and also on the client, when being in a server component just load the next headers and append them to the request.

export const client = hc<AppType>(getBaseUrl(), {
  async fetch(input, requestInit, Env, executionCtx) {
    return fetch(input, {
      ...requestInit,
      ...(typeof window === "undefined"
        ? (await import("next/headers")).headers()
        : {}),
    });
  },
});

I believe your approach will force everything to be dynamically rendered

https://nextjs.org/docs/app/building-your-application/rendering/server-components#dynamic-apis

leognmotta avatar Nov 20 '24 02:11 leognmotta