'use server'
import configPromise from '@payload-config'
import { getPayload } from 'payload'
import { headers as getHeaders } from 'next/headers'
export interface WithAuthProps {
children: React.ReactNode | undefined
}
export const WithAuth: React.FC<WithAuthProps> = async ({ children }) => {
const payload = await getPayload({ config: configPromise })
const headers = await getHeaders()
const { user } = await payload.auth({ headers })
console.info('headers', headers)
console.info('user', user)
return <>{children}</>
}Cannot get user using local api because headers is empty in server component
I am running into a similar issue. In my case I am trying to build a custom auth guard function like the authenticated.ts one. So far I found out that there is a createLocalReq method in payloadcms which creates the req object which is passed there. There the headers are empty. When trying to request the headers using getHeaders everything works in dev-mode, but breaks as soon as you go to a production build as then, in my case, getHeaders is called outside a request context. Here is my issue which might be related:
https://github.com/payloadcms/payload/discussions/11998
Sorry that I can't provide you a solution yet, but it might be worth checking if you are in a request context or not
Thanks for the reply, can you elaborate about the request context? I'm assuming based on your answer that payload isn't forwarding the request headers information to the getHeaders function from nextjs?
Hey, turns out there was a mistake in my part, the component that uses the local api is in a page that uses force static rendering strategy that causes the headers to be empty, changing it to force dynamic solves it.
exactly, force dynamic should resolve it. There should however be a better way without having to make the whole page dynamic, right? At least that's what I feel like. The built-in authenticated hook:
import type { AccessArgs } from 'payload'
import type { User } from '@/payload-types'
type isAuthenticated = (args: AccessArgs<User>) => boolean
export const authenticated: isAuthenticated = ({ req: { user } }) => {
return Boolean(user)
}does work without making the page dynamic because it references the user object of the req object which is passed in there. The request object is however generated by the local api which doesn't include the headers. So there is some way internally somewhere in payload cms where it populates the req object with a valid user object for the built-in auth. Now we would have to figure out how this is done because I feel this is the proper way this is meant to work. Maybe using custom auth strategies? I think that a custom auth strategy should lead to a working user object there and we are just appraoching this problem completely wrong with trying to access the headers directly outside of a custom auth strategy
Btw, how did you force the page to be dynamic?
you can use
export const dynamic = 'force-dynamic'at the top of the page file
for example
import type { Metadata } from 'next/types'
import { CollectionArchive } from '@/components/CollectionArchive'
import { PageRange } from '@/components/PageRange'
import { Pagination } from '@/components/Pagination'
import configPromise from '@payload-config'
import { getPayload } from 'payload'
import React from 'react'
import PageClient from './page.client'
export const dynamic = 'force-dynamic'
export default async function Page() {
const payload = await getPayload({ config: configPromise })
const posts = await payload.find({
collection: 'posts',
depth: 1,
limit: 12,
overrideAccess: false,
select: {
title: true,
slug: true,
categories: true,
meta: true,
},
})
return (
<div className="pt-24 pb-24">
<PageClient />
<div className="container mb-16">
<div className="prose dark:prose-invert max-w-none">
<h1>Posts</h1>
</div>
</div>
<div className="container mb-8">
<PageRange
collection="posts"
currentPage={posts.page}
limit={12}
totalDocs={posts.totalDocs}
/>
</div>
<CollectionArchive posts={posts.docs} />
<div className="container">
{posts.totalPages > 1 && posts.page && (
<Pagination page={posts.page} totalPages={posts.totalPages} />
)}
</div>
</div>
)
}
export function generateMetadata(): Metadata {
return {
title:Internview Posts
,
}
}Thanks, that way I was able to get something running. It's not pretty but it works. I am now doing it by just forcing all pages to render dynamically atm. This is not a long-term solution but works for now.
Star
Discord
online
Get dedicated engineering support directly from the Payload team.