Simplify your stack and build anything. Or everything.
Build tomorrow’s web with a modern solution you truly own.
Code-based nature means you can build on top of it to power anything.
It’s time to take back your content infrastructure.

How to secure the Payload API

default discord avatar
thisizvipinlast year
20

I'm making an landing page for an nextJS 14 app and using payload as a backend. I'm creating blogs in the payload and access them in the frontend app.



Now is there any way to secure these endpoints currently I'm using cors option in the

buildConfig.ts

.


And one option I'm using is custom hook in the collection using

beforeRead

hooks: { afterChange: [revalidatePost], afterRead: [populateAuthors], afterDelete: [revalidateDelete], beforeRead: [ async ({ req, query }) => { const apiKey = req.headers.get('x-api-key') // Check if the API key is provided and matches the one stored in the environment variables if (apiKey !== process.env.PAYLOAD_API_KEY) { throw new Error('Forbidden: Invalid API Key') } return query }, ], },

This approach works but now I've to write this

beforeRead

to every collection, Is there any way to do this globally.

  • default discord avatar
    stofoluslast year

    I think you should read through these docks

    https://payloadcms.com/docs/access-control/overview

    and for API access there's already some support built in

    https://payloadcms.com/docs/authentication/api-keys
  • default discord avatar
    thisizvipinlast year

    I checked these docs but didn't find any solution I'm looking.


    For Access Control I need a user but as it is just a landing page getting data from payload (no user is required).


    API Key strategy is for third party API integration right (which is not my case)



    @332241126150897674

    I tried access control but it didn't worked the api is still accessible from the outside app

  • default discord avatar
    stofoluslast year

    What API are you using? De built in ones?



    Can you try


    access: {
        read: () => false,
      },

    And see if it is accessible



    By default no APIs should be accessible. Unless you're using the local api. Then everything is accessible but only your server code can use that.

  • default discord avatar
    thisizvipinlast year

    I tried this, but it didn't work as expected

  • default discord avatar
    stofoluslast year

    Could you elaborate on how you are accessing the content. Through what API

  • default discord avatar
    thisizvipinlast year

    Can you please explain what local api means here? Because I initialised a payload project and using a separate frontend nextJS app as localhost:3000/api/posts

  • default discord avatar
    stofoluslast year

    Right. Using the access control is still the way to go. Could you just confirm that setting the read access to false stops you from being able to access the API (I know that i breaks the admin ui aswell)

  • default discord avatar
    thisizvipinlast year

    No, API is working and admin panel is also working it's not breaking



    I can share my files here

  • default discord avatar
    stofoluslast year

    Please do

  • default discord avatar
    thisizvipinlast year

    These are my files



    collections/posts/index.tsx

  • default discord avatar
    stofoluslast year

    You have an "authenticatedOrPublished" access function on your Posts collection



    Please have a look here


    https://github.com/stofolus/payload-api-access-demo
  • default discord avatar
    thisizvipinlast year

    so this was the issue?



    cloning

  • default discord avatar
    stofoluslast year

    I've created a separate ApiUser collection. I thinks it's nice to have them separated but you can just use your users if you want. I've added


      auth: {
        useAPIKey: true,
      },

    This will make allow payload to generate an API key and manage all the auth checking.



    Then in your collection you want to check if there is a user. Like this


      access: {
        read: ({ req: { user } }) => {
          if (user) {
            return true
          }
          return false
        },
      },

    If there is a user then payload has authenticated someone based on either user / password or API key. Here you can also check what kind of user. You can add roles to the user collection and check them here and then return true / false depending on what your need are



    And for your earlier question


    API Key strategy is for third party API integration right (which is not my case)

    It's not. It's for when you want to use API keys to interact with the payload APIs or any other APIs you use where you might want to leverage their already built auth solution



    First or third party does not matter



    Also on a side note. The website template is very advanced. It can be really hard to get a grasp on everything and customizing it into what you really need. So my personal suggestion is to never start a project from it. Instead use it for understanding how things can be implemented and cherry-pick the parts that you need and understand



    The api-user collection should probably have

    disableLocalStrategy: true,

    aswell to disable username / password login

  • default discord avatar
    thisizvipinlast year

    Thanks, for the help



    I cloned the code there was a minor CORS issue for which I had to allow my frontend origin, now If I access API from my frontend it gives 403 error and from admin UI it works as expected



    I'm still not able to get it how it'll work?


    with all this logic inplace payload will need user/apiKey to authenticate right, in my case my frontend doesn't have any user/apiKey so payload will not authenticated and gives 403 forbidden



    Here's video

  • default discord avatar
    stofoluslast year

    I think you've missunderstood. The

    useAPIKey

    goes on a user collection. So that the users can have an API key



    If you just want your frontend to be able to get your posts just set read to () => true



    And it will always be readable



    If you want to have an api key you add the useAPIkey to your users collection and go into the UI and get the key from the user



    If you run my demo. Create a few pages and then try to access them from postman / insomnia through the api with an API key and it might become clearer how it works

  • default discord avatar
    thisizvipinlast year

    I created few pages now, I get the data if the read returned value is true (doesn't matter if I check user or explicitly set true). I also enabled the API key and it's value how can I use this so I can send this key in the headers from the separate frontend or postman so that payload authenticates this key

  • default discord avatar
    stofoluslast year

    You can use it like this



    const response = await fetch('http://localhost:3000/api/pages', {
      headers: {
        Authorization: `${Users.slug} API-Key ${YOUR_API_KEY}`,
      },
    })


    That's the whole point. You own the function. You return true if you want to allow the request and false if not. The logic is all up to you

  • default discord avatar
    thisizvipinlast year

    Thank you so much

  • default discord avatar
    swordsreversed5139last year

    Hi

    @332241126150897674

    im hoping you can help me with this. I've added a user called Apiuser and set up a key for it. when i attempt to call a collection i get "You are not allowed to perform this action." Also if i log the key it is shows only the Admin user which is the wrong user?

  • default discord avatar
    stofoluslast year

    Can you tell me more? What endpoint are you calling? A built in or custom? How are you logging the key? Can you show some code?

Star on GitHub

Star

Chat on Discord

Discord

online

Can't find what you're looking for?

Get dedicated engineering support directly from the Payload team.