Next.js Discord

Discord Forum

Authentication help

Unanswered
finesse posted this in #help-forum
Open in Discord
I use Supabase for my auth, I previously used middleware to protect pages, based on this documentation: https://supabase.com/docs/guides/auth/server-side/creating-a-client

However, I recently learned/ read that middleware protection is not a 100% secure way to protect my pages. I also am using cacheComponents.

I have come up with this solution and wanted to run it by everyone here. I now use middleware + this solution below + user checking in my database calls. Feel free to give feedback on my solution, preferably security focused!

first, I have made a verifyAuth function similar to NextJs docs: https://nextjs.org/docs/app/guides/authentication#authorization
export const verifyAuth = cache(async () => {
    const supabase = await createSupabaseServerClient()
    const user = await supabase.auth.getUser()

    if (!user) {
        console.log("no user fail")
        redirect("/admin/login?=unauthorized")
    }
    if (user && user.data.user?.id !== "specific id, because for now I have only one user (admin) and if someone somehow made an account, this is extra protection") {
        console.log("incorrect user")
        await supabase.auth.signOut()
        redirect("/admin/login?=unauthorized")
    }
    console.log("authenticated!")
})


next, I have a simple component that calls my verifyAuth function, and passes down the children:
export default async function VerifyAuth({children}: { children: ReactNode }) {
    await verifyAuth()
    return <>{children}</>;
}

finally, In my protect pages suspense, I wrap the Components I want to protect in the VerifyAuth component:
export default async function AdminDashboard() {
    return (
        (
            <Stack align={"center"} p={"lg"}>
                <Suspense fallback={<TableLoading/>}>
                    <VerifyAuth>
                        <TableWrapper/>
                    </VerifyAuth>
                </Suspense>
            </Stack>
        )
    )
}

4 Replies

Please give me opinions if possible!
Sun bear
This will work but it's better to not complicate as much and do auth check on page level. For client components that require access to user object you can use context or state managment library such as jotai.

Here is the simplest form of how I generally handle my auth:

async function getUser() {
  return user ?? null
}

async function AdminPage() {
  const user = await getUser()

  if (!user) redirect("/login")

  return (
    <UserContextProvider user={user}>
      <AdminDashboard />
    </UserContextProvider>
  )
}

// then in client components you can access the user object via use(UserContext)

function UserProfile() {
  const user = useUserContext()

  return <div>{user.username}</div>
}
Abstractions are good but for things like auth you should start off simple then when the logic gets too repetitive look for a better way to package it.
@Sun bear This will work but it's better to not complicate as much and do auth check on page level. For client components that require access to user object you can use context or state managment library such as jotai. Here is the simplest form of how I generally handle my auth: tsx async function getUser() { return user ?? null } async function AdminPage() { const user = await getUser() if (!user) redirect("/login") return ( <UserContextProvider user={user}> <AdminDashboard /> </UserContextProvider> ) } // then in client components you can access the user object via use(UserContext) function UserProfile() { const user = useUserContext() return <div>{user.username}</div> }
I use cache components. Here is an simple auth check on my admin page:
export default async function AdminDashboard() {
    const supabase = await createSupabaseServerClient()
    const user = await supabase.auth.getUser()

    if (!user) {
        redirect("/admin/login?=unauthorized")
    }
    
    return (
        (
            <Stack align={"center"} p={"lg"}>
                <Suspense fallback={<TableLoading/>}>
                    <TableWrapper/>
                </Suspense>
            </Stack>
        )
    )
}

When I do this, I get this error:
installHook.js:1 Error: Route "/admin/dashboard": Uncached data or connection() was accessed outside of <Suspense>. This delays the entire page from rendering, resulting in a slow user experience. Learn more:

This is pretty much telling me that the page has to wait for the auth call before loading, and this causes me build errors.
This is what made me want to check auth inside the suspense, but I am wondering if theres a way around this.
My table wrapper does a db call to get data, and inside the data call it also checks auth, but this is in the server action