I have a frontend NextJS app, login and logout work, sets the http-only cookie and removes it. Collection Users have the default access: { read: () => true, }.
When I try to fetch other collections I get the not allowed error, but Postman and the url in the browser work after login.
I have cors and csrf setup and when I fetch credentials: "include" otherwise login and logout wouldn't work.
Can you please help me? This is driving me insane.
EDIT: this is happening in localhost, so no issues with https like I've seen others had in production
access: { read: () => true }
describes the access to the collection where you add it. So when you add it to the
users
collection. Everyone can read the users in your payload via the API
/api/users
.
Did you also add the access:read to the other collections? 🙂
Yes I understand that. I want everything on my app to be only accessed by authenticated users, so I don't want anything public. I left access true to users during this testing because it was default.
I'm using the app router of NextJS 13 and fetching data from the server, so I think something weird is happening.
If I put "use client" on the page then it works, but it's unreliable because not supported from nextjs, infact sometimes I get multiple rerender loops and sometimes I get cors errors, even if I have my localhost cors setup, like you can see in the screenshot, I also tested 0.0.0.0:4000 which is the server address I see when starting nextjs dev, but it's not making a difference
Now I tried to send the cookie with the headers when fetching and it works. Isn't "credentials include" supposed to do that automatically? It's doing it with login and logout successfully.
If you are making a fetch from the server component (not using 'use client') then it's the server making the fetch, not the browser (where the cookie is set).
When you forward the cookie with the headers, then you are handing it from the browser, to the server, to make the fetch.
I'd try cleaning up your buildConfig to just include your front end in the cors & csrf (take the time and set up your .env)
With multiple cors/csrf values, I've found in the past using .filter(Boolen) helps. I'm not sure if its required these days, but it caused issues for me before, so I include it in all of my configs.
export default buildConfig({
serverURL: process.env.PAYLOAD_PUBLIC_URL,
cors: [process.env.FRONTEND_URL].filter(Boolean),
csrf: [
process.env.PAYLOAD_PUBLIC_SERVER_URL,
process.env.FRONTEND_URL,
].filter(Boolean),
admin: {
user: Users.slug,
},
...
Oh yeah now it makes sense. The server can't read the cookie unless I tell him to. So I guess that's the only way when using server components.
I haven't set up the env yet because I wanted to test things out first, I'll do that now and follow your advice. What's the difference between: PAYLOAD_PUBLIC_URL and PAYLOAD_PUBLIC_SERVER_URL ?
Ah good spotting, they should be the same thing, but I have called them something different in different projects which I pasted code into. The PAYLOAD_PUBLIC_SERVER_URL doesn't actually get used in my case.
Should look like this;
serverURL: process.env.PAYLOAD_PUBLIC_URL,
cors: [process.env.FRONTEND_URL].filter(Boolean),
csrf: [process.env.FRONTEND_URL].filter(Boolean),
),
perfect, thanks
I cleaned up my configs, removed rateLimit which was just an attempt after following other people's problems, and set up the env, but it still doesn't work.
The problem that the server doesn't know the cookie remains, and I can't use nextjs fetch on the client because it couses rerenders infinite loops
A more elegant way of passing the cookie for each page and component request could be using context, but that's a client only thing, like state, so then I can't fetch from client. I knew that moving to the app router would cause issues...
Using client side http-only cookies is a completely valid way of handling authentication. I use it often, and indeed the payload auth example uses that strategy as well - have you seen that? I would imagine you'll only find it more frustrating to put the auth completely server-side using nextjs 13.
If I were you I would try to work out what's causing the re-rendering issue. That's not normal, and I bet if you work out that, then you'll be on your way.
The whole point of the app router of nextjs 13 is that we can use server component and they recommend to fetch data on the server, infact fetch() "is currently not recommended in Client Components and may trigger multiple re-renders"
https://nextjs.org/docs/app/building-your-application/data-fetching/fetching#use-in-client-components, until they say it's supported I don't think it's work exploring why it causes re-render, I built a test page that only fetches data and logs it, when I fetch a collection with only 2 docs it works, when its 30 it starts to re-render. The alternative to fetch on the client is SWR or react query, which I've never used, but I could try. In the meantime I wanted to stick to server components when possible, is it a problem to read the cookie and setting the header for every request? It's only 2 lines of code
I would disagree that the whole point of nextjs 13 is to use server components. Client components are still super critical, and Vercel by all means do not suggest that success is having all of your app server rendered. Yes server components are very valuable for sending pre-rendered content, and leveraging caching, etc. But they work best when they know what they are rendering ahead of time, before the request comes in (so that they can cache stuff). You
cando the no-cache route and make everything dynamic, and forward all your cookies and then pass back response, but that's kind of double handling everything... It does make things a more secure, only if you handle things correctly on the server.
Or you can keep the auth handling on the client, and leverage other client component stuff like, state. It's not uncommon to only render components if a user is logged in, right?
Don't use fetch on the client, as warned againt by the documention. SWR would be your best bet, and it's super simple to use. Here's an example;
'use client';
import { CalendarIcon, MapPinIcon } from '@heroicons/react/20/solid';
import useSWR from 'swr';
const fetcher = (url: string) =>
fetch(url, { credentials: 'include' }).then((res) => res.json());
function meetingData() {
const { data, error, isLoading } = useSWR(
'${process.env.NEXT_PUBLIC_CMS_URL}/api/meetings?sort=dateTime',
fetcher
);
return { data, error, isLoading };
}
export default function Calendar() {
const data = meetingData();
const meetings: {
id: string;
meetingType: string;
dateTime: string | Date;
location: string;
}[] = data?.data?.docs;
if (data.error) return 'An error has occurred.';
if (data.isLoading) return 'Loading...';
return(<></>)
Got it, thank you very much for the suggestion. The app I'm currently trying to move to the app router actually uses a lot of state and context, so I will inevitably need to render in the client a lot. I'll give SWR a go.
I have to change the login and logout fetch as well, because that was apparently working on the client
Makes sense. The payload auth example has the whole pattern and is really good. It has the auth provider that you can wrap around your app so that you can do client authy stuff where ever you like. It has all the login and logout function you’ll ever need basically.
Yes I've followed that example to set up login and logout already, but done it differently since I'm on the app router. In the example it fetches data with useEffect, now that I've started using SWR I'll make use of that everywhere.
I learned SWR was only intended to be used for GET requests, now with version 2 we can do mutations, but I'm still struggling to adapt it to the simple authentication I have already in place, which is using fetch client side and works with no issues. Login and Logout in Payload are POST request, is it possible that fetch client-side actually works for post and only has issues with get? In my app I will need to have many create, update and delete operations, so I need a way of having POST, PATCH, DELETE requests and I'm not sure SWR is the best way for that. What's the most used library to do all of these operation? When I was using GraphQL, the Apollo client was covering everything.
You probably don't need a library, you could use the fetch api to do the other request types, there's an example in the payload auth example with the account update page.
https://github.com/payloadcms/next-auth-frontend/blob/0afca1bf807dd09951dfe4af4568f32b8d7a9362/pages/account/index.tsx#L27That is precisely what I'm doing for login and logout, I followed that example, and it works great. But I'm using fetch inside a client component and as we discussed before it's not supported in nextjs 13 app router. I feel like there's something I'm missing...
EDIT: does that mean fetch api is not supported for GET requests, but it is for other request types? Seems counter-intuitive
Fetch doesnt do POST inside client components? That doesn't seem right
Are you getting an error?
Fetch does do POST inside a client component without issues, I'm using it for login and logout, everything works. But as mentioned here:
https://nextjs.org/docs/app/building-your-application/data-fetching/fetching#use-in-client-componentsit's not supported in NextJS 13 app router. In fact when I was doing a fetch GET on a different page, I was having some rerendering issues, which is why I switched to SWR for that. Now I'm not sure if using fetch for POST is still fine. Always talking about client components with the app router
The docs you linked, I believe are referring to server components using fetch
your component has
use client
at the top of it?
also SWR is so bad, I cannot believe they did not support mutations for so long
" For now, if you need to fetch data in a Client Component, we recommend using a third-party library such as SWR or React Query." that to me means fetch is not supported on client component. I did some testing and for server component worked without issues, but as soon as I put "use client" on top of it I started getting rerendering issues, exactly what they mention. For some re,ason my login and logout are working and they also have "use client". Not sure why, but I was trying to switch to SWR because it's recommended by vercel and above by
@981053249098498138. Why do you think it's bad? I've only tested it a little and I really like it for GET reqeusts, but I still don't understand how to make a POST request or if I need to use their mutation hook.
I just think tanstack query is much more mature
and I also hate when vercel/next ships half baked products
Also - we are using a POST request in the app folder, in a
use client
component here:
https://github.com/payloadcms/website/blob/main/src/app/cloud/%5Bteam-slug%5D/%5Bproject-slug%5D/(tabs)/(overview)/infraOnline/index.tsx#L45and SWR might be better nowadays, but I when I used it before it had mutations - their docs had a section about mutations but it really did not have the mutations you would need for a real app. My haste stems from me switching a project from tanstack over to SWR only to find out that I could not do all the things that I was doing with tanstack.
curious, is the .then pattern preferred over async/await?
honestly I have no preference haha. They both do the same thing
This is true, I pesonally like async/await because it has less nesting, but i could be wrong
but yeah normally we use async/await not sure what the decision was here to not use it
ahh i see
I have more of a problem that it is not using .catch
same thing when ppl use async/await and don't try/catch them
ah yea, u gotta try catch
Oh, I didn't know the frontend of payload was built with nextjs app router, I'll definitely take a look. I see you are using fetch with both POST and GET on client components, so I'm not sure why I had problems. To be honest it was working when fetching a collection with only two elements but got infinite rerendering when fetching a collection with 10 elements, then I read it was not supported and looked for something else.
Alright, here's the most basic example of fetch inside a "use client" page. If I fetch from a collection with 1 doc it works, if I do to one with 10 docs it gets into an infinite rerendering loop and I have to stop payload. If I remove the "use client" it works, but since it's now a server component it can't include the credentials, so I have to manually read the cookie and pass them header. What do you think is causing it? I voluntarily made a very simple fetch that logs the data.
it works in Postman or similar?
yes, works on postman
really hard to say then haha
although your file does look a little odd
you are exporting an async component in a
use client
file. Can you screenshot the actual code (server component) you are having trouble with?
This is the code of the page I'm having trouble with, there's no other component, I tried to make it as basic as possible as a test to find the issue. It's the most basic example of fetching data from here:
https://nextjs.org/docs/app/building-your-application/data-fetching/fetchingI just added "use client" and the try-catch. Maybe that's the issue, if it's a "use client" page I have to do things differently??
I am getting this same error when accessing media or uploads that doesn't exist in the folder.
Can you post your error? Happy to help check it out
Forbidden: You are not allowed to perform this action.
at /Users/###/Typescript - Practice/####/node_modules/payload/src/auth/getExecuteStaticAccess.ts:53:17
at processTicksAndRejections (node:internal/process/task_queues:95:5)
Can you share your user collectiona access control settings please?
sure
labels: {
singular: "Media",
plural: "Media",
},
admin: {
enableRichTextRelationship: true,
description: "No selfies please",
group: "Media Files",
},
access: {
create: () => true,
read: ({ req: { user } }) => {
if (checkRole(["admin"], user)) return true;
if (checkRole(["operator"], user)) {
if (user) {
return {
owner: {
equals: user.id,
},
};
}
return false;
}
return true;
},
update: ({ req: { user } }) => {
if (checkRole(["admin"], user)) return true;
if (user) {
return {
id: user.id,
};
}
return false;
},
delete: access,
admin: () => false,
},
Hmm, on first look it seems like an issue with the read function. Does it work if you set that to
() => true
for testing?
Star
Discord
online
Get dedicated engineering support directly from the Payload team.