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.

Fetch user from custom endpoint

default discord avatar
___lloydlast year
90

Hello guys, I have a question about fetching user data from a custom endpoint.



I'm making a website that uses payloadcms as backend. I have kept the default authentication method, which I believe uses JWT.



After the user successfully signs in in the front-end with this

const response = await axios.post('/backend/api/users/login', credentials)

he gets assigned a cookie, which helps me keep the user session alive.



The problem is that I'm trying to make the user send a request like this


const reqFinishedSimulation = axios.post(
        '/backend/api/simulations-history/v1/correction',
        finishedSimulationReduxState,
        { withCredentials: true },
      )

It has

withCredentials

set to true, which means that it will send the user cookie in the headers.



The link

/backend/api/simulations-history/v1/correction

corresponds to a custom endpoint in payloadcms


endpoints: [
{
  path: '/v1/correction',
  method: 'post',
  handler: async (req, res, next) => {
    
  }
}
]

From this endpoint, I want to get the userID from the

req

. I can't submit the userID inside the request, because that would mean that if a user is logged in, the endpoint will accept any userID parameter, and a malicious user could change the userID in the request and submit ids of other users.



Is it possible to get the userID from a

req

object in a custom endpoint?



Thank you

  • default discord avatar
    paulpopuslast year

    The request object should have a user object on

    req.user

    Is this not present?

  • default discord avatar
    ___lloydlast year

    Unfortunately it is not present, I have searched online and done some tests before posting the question here 😦



    All I was able to find in the

    req

    object is the cookie and the payload jwt token



    Correction: only the cookie, the token is already inside the cookie

  • default discord avatar
    paulpopuslast year

    So when you make a request to a payload-managed endpoint (eg youre not circumventing payload via express directly) it passes through the auth middleware and the user is added to the request context

  • default discord avatar
    ___lloydlast year

    Wait I'll try to make a request to a payload managed endpoint with the same axios request that uses

    withCredentials

    to see if it submits the user object in the request

  • default discord avatar
    paulpopuslast year

    I'm trying to think of what it could be, if you inspect the fetch in your frontend



    Yeah that's what im thinking...axios isnt sending the cookie for w/e reason

  • default discord avatar
    ___lloydlast year

    No no, the cookie is being sent, but I don't know how to handle the cookie in the custom endpoint to get the userID directly in the backend

  • default discord avatar
    paulpopuslast year

    You dont need to handle the cookie, it's automatically handled for you and the relevant user is added to the req object

  • default discord avatar
    ___lloydlast year

    Oh, then just a second that I try to check a request to a payload managed endpoint

  • default discord avatar
    paulpopuslast year

    Yeah good idea! try the

    me

    endpoint

  • default discord avatar
    ___lloydlast year

    I actually tried it before, and it attaches the

    user

    object to the request, but I thought that that endpoint works differently



    It was not an axios request tho, I just went here directly from the browser that had the cookie for the logged in user

  • default discord avatar
    paulpopuslast year

    the

    req.user

    object is only missing if the request couldn't be authenticated



    hence you can use it as a check to limit access on your endpoint



    try a straight fetch without axios



    const form = new FormData()
        form.append('unset', 'true')
    
        const options: RequestInit = {
          method: 'POST',
          body: form,
          credentials: 'include',
        }
    await fetch(`url`, options)


    excuse the formatting

  • default discord avatar
    ___lloydlast year

    Okay, just 1 sec

  • default discord avatar
    paulpopuslast year

    ^ this is what i used for one of my custom endpoints so ik it works

  • default discord avatar
    notchrlast year

    and even better in a try/catch πŸ˜„



    hides
  • default discord avatar
    paulpopuslast year

    i handled my error with a catch trail 😀

  • default discord avatar
    notchrlast year

    Fair fair ❀️

  • default discord avatar
    ___lloydlast year

    I did it, it has sent the cookie, but still no

    user

    object in the request

  • default discord avatar
    paulpopuslast year

    are you sending this from localhost to localhost or is this on a server?

  • default discord avatar
    ___lloydlast year

    from localhost to localhost



    To be precise, the server is on a virtualmachine, and I forwarded the ports so I can connect to the server using

    localhost:3000

    (fontend) and

    localhost:3001

    (backend)

  • default discord avatar
    paulpopuslast year

    and is the

    /me

    endpoint returning just fine or does that not work as well?



    try {
      const req = await fetch('{cms-url}/api/{user-collection}/me', {
        method: "GET", 
        credentials: "include",
        headers: {
          "Content-Type": "application/json",
        },
      })
      const data = await req.json()
    } catch (err) {
      console.log(err)
    }

    like this

  • default discord avatar
    notchrlast year

    Also are your cors/csrf configs set?



    Not sure if that's relevant here

  • default discord avatar
    ___lloydlast year

    the

    /me

    works as expected. I have just checked the requests in the browser and hte

    user

    is included

  • default discord avatar
    notchrlast year

    Oo nice



    @858693520012476436

    is killin it πŸ˜„

  • default discord avatar
    ___lloydlast year

    {


    "user": {


    "id": "63e1fae9af38ad9ec568f1e1",


    "firstName": "John",


    "lastName": "Wick",


    "instagram": "@abcdefghi",


    "phone": 44447765,


    "birthday": "1970-06-24T00:00:00.000Z",


    "gender": "1",


    "email": "dev@email.com",


    "createdAt": "2023-02-07T07:16:57.806Z",


    "updatedAt": "2023-06-13T15:42:39.632Z",


    "referral": {},


    "_strategy": "local-jwt"


    },


    "collection": "users",


    "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImRldkBlbWFpbC5jb20iLCJpZCI6IjYzZTFmYWU5YWYzOGFkOWVjNTY4ZjFlMSIsImNvbGxlY3Rpb24iOiJ1c2VycyIsImlhdCI6MTY4NjY3MDk1OSwiZXhwIjoxNzU4NjcwOTU5fQ.jfQY_rPK3bHZ1zTPycrMsPghzV9xj3ctaLg2H_ZU-go",


    "exp": 1758670959


    }



    This is the preview of the request to

    /me

    It has everything in it

  • default discord avatar
    notchrlast year

    wait that's the response right?

  • default discord avatar
    ___lloydlast year
    {
        "user": {
            "id": "63e1fae9af38ad9ec568f1e1",
            "firstName": "John",
            "lastName": "Wick",
            "instagram": "@abcdefghi",
            "phone": 44447765,
            "birthday": "1970-06-24T00:00:00.000Z",
            "gender": "1",
            "email": "dev@email.com",
            "createdAt": "2023-02-07T07:16:57.806Z",
            "updatedAt": "2023-06-13T15:42:39.632Z",
            "referral": {},
            "_strategy": "local-jwt"
        },
        "collection": "users",
        "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImRldkBlbWFpbC5jb20iLCJpZCI6IjYzZTFmYWU5YWYzOGFkOWVjNTY4ZjFlMSIsImNvbGxlY3Rpb24iOiJ1c2VycyIsImlhdCI6MTY4NjY3MDk1OSwiZXhwIjoxNzU4NjcwOTU5fQ.jfQY_rPK3bHZ1zTPycrMsPghzV9xj3ctaLg2H_ZU-go",
        "exp": 1758670959
    }


    this is the response to

    /me

    request

  • default discord avatar
    notchrlast year

    Ah okay cool

  • default discord avatar
    paulpopuslast year

    Yeah but in your own custom handler the

    req

    object doesnt have a user?



    like

    req.user

    is just undefined?

  • default discord avatar
    ___lloydlast year

    Exactly, it's undefined


    All I was able to get in the

    req

    is the cookie, but no user object

  • default discord avatar
    notchrlast year

    Did you call payload.authenticate() ?



    Not sure if this is localAPI or not

  • default discord avatar
    paulpopuslast year

    No you dont need to do that typically



    It passes through a middleware

  • default discord avatar
    notchrlast year

    Woah really?

  • default discord avatar
    ___lloydlast year

    I have tried to console log it but it just said

    function

    I was not sure how that worked

  • default discord avatar
    notchrlast year

    Yeah you wouldnt log it



    But

    @858693520012476436

    are you sure that calling payload.authenticate() isnt necessry for their situation?



    I'm interested in seeing their payload.config.js

  • default discord avatar
    paulpopuslast year

    Well not exactly but I wanna get there first, all custom endpoints that dont circumvent payload pass through the necessary middleware for auth

  • default discord avatar
    notchrlast year

    Ahhh okay, I dont mean to butt in, you got this πŸ™‚

  • default discord avatar
    paulpopuslast year
  • default discord avatar
    ___lloydlast year

    Yes, it's called SimulationsHistory, I added it to payload.config



    import { CollectionConfig } from 'payload/types'
    import payload from 'payload'
    import axios from 'axios'
    import fs from 'fs'
    
    import type { Solution, FinishedSimulation } from '../custom-types'
    
    const SimulationsHistory: CollectionConfig = {
      slug: 'simulations-history',
      admin: {
        useAsTitle: 'id',
        group: 'Simulations Editor',
      },
      labels: { singular: 'Simulation History', plural: 'Simulations History' },
      access: {
        read: () => true,
        create: () => true,
        update: () => true,
        delete: () => true,
      },
      endpoints: [
        {
          // This is the endpoint that will receive the finished simulation, correct it, and either return an error or create a new record in the simulations history
          // The sections and the questions should keep their original order inside the simulation history
          path: '/v1/correction',
          method: 'post',
          handler: async (req, res, next) => {
            const simulation: FinishedSimulation = req.body
             console.log(req)
    
          },
        },
      ],
      fields: [
        {
          type: 'text',
          name: 'title',
        },
      ],
    }
    
    export default SimulationsHistory


    It doesn't have any fields because I wanted to solve the issue with not finding the user first

  • default discord avatar
    paulpopuslast year

    This is on the latest payload right?



    Just covering the bases here πŸ˜…

  • default discord avatar
    ___lloydlast year

    "node_modules/payload": {


    "version": "1.8.3",



    I updated it quite recently

  • default discord avatar
    paulpopuslast year

    not quite the latest but it should be fine, i'd do a

    yarn upgrade

    anyway just to be safe



    Wanna try adding your endpoint to the payload config instead? So it's not attached to your collection, my working examples are all built that way so I wanna see,

  • default discord avatar
    ___lloydlast year

    I'm not sure how to do that..



    We are talking about

    payload.config.ts

    that is inside the root directory right?



    src directory**

  • default discord avatar
    notchrlast year
    https://payloadcms.com/docs/configuration/overview

    The payload.config.ts has the config object that also accepts 'endpoints;'

  • default discord avatar
    paulpopuslast year

    Yes you can add a new key for

    endpoints

    which is an array of endpoints similar to the one you wrote



    the difference will be that the path wont be hoisted to the collection



    so it will be

    backend/api/PATH

    i just wanna see if the

    req.user

    is being correctly added there

  • default discord avatar
    ___lloydlast year
    export default buildConfig({
      serverURL: 'http://localhost:3001',
      admin: {
        user: Admins.slug,
      },
      endpoints: [
        {
          path: '/v1/pleasework',
          method: 'get',
          handler: async (req, res, next) => {
            console.log(req)
          }
        }
      ],
      collections: [
        Admins,
        Users,
        Courses,
        Lessons,
        Exercises,
        Questions,
        QuestionsMedia,
        ManualSimulations,
        UserStatistics,
        // GeneratorCourses,
        // GeneratorSubjects,
        // GeneratorTopics,
        // Generators,
        QuestionsHistory,
        GeneratedSimulations,
        SimulationsHistory,
        UserInventories,
        UserCourses,
        CourseMedia,
        ExerciseQuestions,
        UserLastLessons,
        UserGlobalLastLessons,
        UserCompletedManualSimulations,
        QuestionCourseLabels,
        QuestionSubjectLabels,
        QuestionTopicLabels,
      ],
      typescript: {
        outputFile: path.resolve(__dirname, 'payload-types.ts'),
      },
      graphQL: {
        schemaOutputFile: path.resolve(__dirname, 'generated-schema.graphql'),
      },
      // cors: ['http://localhost:3001', 'http://10.119.143.222:3001', '127.0.0.1:3001'],
      // // cors: '*',
      debug: true,
    })

    The above code is inside payload.config



      useEffect(() => {
        async function abc() {
          const form = new FormData()
          form.append('unset', 'true')
      
          const options: RequestInit = {
            method: 'GET',
            // body: form,
            credentials: 'include',
          }
          await fetch(`/backend/api/v1/pleasework`, options)
        } 
    
        abc()
      }, [])

    The code above is inside nextjs to make the request when I visit a page




    I confirm that now I can see the

    user

    object inside

    req

    ! But this is a GET request, I will try really quick with a POST one to see if I can still see the user



    In the POST request the user object is not there anymore

  • default discord avatar
    notchrlast year

    weird

  • default discord avatar
    paulpopuslast year

    It

    should

    work, its baffling

  • default discord avatar
    notchrlast year

    How would GET work and not POST



    seems dubious

  • default discord avatar
    paulpopuslast year

    Anything in the network tab for the post?

  • default discord avatar
    ___lloydlast year

    I have made 2 separate endpoint for get and post to see exactly what's the difference



    Both are delivered successfully, they differ in some headers, like the content-type in the POST request which is not needed in the GET



    In the Request, the GET is empty, the POST has this


    -----------------------------40592849741449068010483352937
    Content-Disposition: form-data; name="unset"
    
    true
    -----------------------------40592849741449068010483352937--
  • default discord avatar
    paulpopuslast year

    hmm your credentials are not included πŸ€”

  • default discord avatar
    ___lloydlast year

    But as far as the browser goes, I'm not able to see the

    user

    object attached to the request, I can only see it in the

    req

    that is console logging in the backend terminal



    Should they? I'm using the empty post request that you supplied earlier

  • default discord avatar
    notchrlast year

    are you passing credentials: 'include'

  • default discord avatar
    ___lloydlast year

    Ahh, yes



      useEffect(() => {
        async function abcget() {
          const form = new FormData()
          form.append('unset', 'true')
      
          const options: RequestInit = {
            method: 'GET',
            // body: form,
            credentials: 'include',
          }
          await fetch(`/backend/api/v1/pleaseworkget`, options)
        } 
    
        abcget()
    
        async function abcpost() {
          const form = new FormData()
          form.append('unset', 'true')
      
          const options: RequestInit = {
            method: 'POST',
            body: form,
            credentials: 'include',
          }
          await fetch(`/backend/api/v1/pleaseworkpost`, options)
        } 
    
        abcpost()
      }, [])
  • default discord avatar
    paulpopuslast year

    and its included for get and not for post?

  • default discord avatar
    ___lloydlast year

    Yes, wait I'll make a pastebin or similar so I can show you the console log, it's too long for discord

  • default discord avatar
    notchrlast year

    Never seen a bug like this, wonder what's causing it

  • default discord avatar
    ___lloydlast year
    https://pastebin.com/vugRqpDh

    the password for it is Z39gcUJgp5



    I have separated the get and post request with a long

    ===========================

    And each begin with

    this is the post request

    and

    this is the fet request
  • default discord avatar
    notchrlast year

    Hmmm



    The JWT on the response has the following data



    {
      "email": "dev@email.com",
      "id": "63e1fae9af38ad9ec568f1e1",
      "collection": "users",
      "iat": 1686685431,
      "exp": 1758685431
    }
  • default discord avatar
    ___lloydlast year

    Yes, that's the get request



    The get requests has the

    user

    , but the post request does not

  • default discord avatar
    notchrlast year

    POST JWT: {


    "email": "dev@email.com",


    "id": "63e1fae9af38ad9ec568f1e1",


    "collection": "users",


    "iat": 1686685431,


    "exp": 1758685431


    }



    looks the same?

  • default discord avatar
    paulpopuslast year

    If get works, then post should work too...this is the bit that doesnt make sense to me

  • default discord avatar
    ___lloydlast year

    wait, what line is that?

  • default discord avatar
    notchrlast year

    Go to both requests



    grab the payload-token



    then go to

    https://jwt.io

    paste the token



    both requests have the same data in the token



    or responses rather

  • default discord avatar
    ___lloydlast year

    Oh, so you decoded the token?

  • default discord avatar
    notchrlast year

    well anyone could, but it's not "untampered" unless it validates with the secret token



    so you know that it originates from a specific source



    But the point is



    Both requests/responses have the user in question in the JWT



    which makes me think that both work, something is going on with the code



    Paul may be more helpful at this point tho, I gotta drive home



    :*(

  • default discord avatar
    ___lloydlast year

    Allright, thank you a lot for the help ❀️


    drive safe

  • default discord avatar
    notchrlast year

    Thank you! Hopefully you can figure this out!

  • default discord avatar
    paulpopuslast year

    im out of ideas, checking in with chat gpt 4 on this one



    hold on

  • default discord avatar
    ___lloydlast year

    Are we sure that the

    user

    object should be attached to the

    req

    object in a POST request?

  • default discord avatar
    paulpopuslast year

    to your post can you add a headers?



    headers: { 'Content-Type': 'application/json' },


    I'm betting my bugattis on it



    So if the headers doesnt do it, can you inspect your cookie for me in the browser and lmk what config it has? you should see a

    payload-token

    cookie with some attributes like

    sameSite

    httpOnly

    etc.



    in your payload config I noticed youre missing

    cors

    and

    csrf

    config, these may be related 😬



    set them both to an array of localhost:3000 and 3001

  • default discord avatar
    ___lloydlast year

    Just tried the headers, doesn't work



    Actually I had some fight with that when I started coding the project. Basically to access my backend i'm using a NextJS rewrite, that goes like this


    rewrites: async () => {
        return [
          {
            source: '/backend/:path*',
            destination: `${process.env.BACKEND}/:path*`
          }
        ]
      },


    So it makes the code think that everything is on

    localhost:3000

    (the frontend)


    otherwise my login wouldn't work at all



    But I can try to restore the cors and csrf

  • default discord avatar
    paulpopuslast year

    OH.

  • default discord avatar
    ___lloydlast year

    Is it an "oh" in a bad way?

  • default discord avatar
    paulpopuslast year

    It's possible it's related yeah πŸ€”

  • default discord avatar
    ___lloydlast year

    (btw im looking a second through payload docs to see how to add cors and csrf)



    Ohw, I thought that if

    /me

    worked then the config was good

  • default discord avatar
    paulpopuslast year

    should both be like

    cors: [],

    and

    csrf: []

    arrays of strings



    I just wanna make sure, nextjs has been less than stellar recently for me



    numerous bugs

  • default discord avatar
    ___lloydlast year

    Ok I did this


      csrf: ['http://localhost:3001', 'http://localhost:3000'],
      cors: ['http://localhost:3001', 'http://localhost:3000'],

    I'll test it now



    Still only works with GET

  • default discord avatar
    paulpopuslast year

    Can you try posting straight to payload, skipping next?



    like, login first so you get the cookie set



    and then do your endpoint



    all from straight to payload...this is easier with postman cause you can more quickly make these requests

  • default discord avatar
    ___lloydlast year

    Okay, I'll try, just a thing, is it enough if I copy to postman the cookie I get from signin in directly into payload?


    I use it to test my api but I'm not really good with it

  • default discord avatar
    paulpopuslast year

    I use insomnia, so idk the UI exactly but you can make it so it captures the token



    it captures the cookie just like a browser would



    and it can send them on other requests

  • default discord avatar
    ___lloydlast year

    Ohh I get it

  • default discord avatar
    paulpopuslast year

    but if the browser is easier for you then do that



    i just wanna eliminate the rewrite being a problem

  • default discord avatar
    ___lloydlast year

    So I do a post request to /login with the credentials so it saves the cookies



    (from postman)

  • default discord avatar
    paulpopuslast year

    you can do it from your frontend, just skip nextjs



    as in the rewrite, so do a fetch to

    payload/api

    wherever that sits

  • default discord avatar
    ___lloydlast year

    I did the request straight from postman to

    /v1/pleaseworkpost

    and the

    user

    object showed in the request

    :chefskiss:
  • default discord avatar
    paulpopuslast year

    ok its not payload's fault! >:)

  • default discord avatar
    ___lloydlast year

    I can't thank you enough for this, I don't know if I would have been able to troubleshoot by myself

  • default discord avatar
    paulpopuslast year

    it's possible your cookie being POSTed through a rewrite is either lost there or discarded by payload's middleware if the origin isnt quite right



    chat gpt 4 helped us..confirmed that there are different situations where a GET and a POST with credentials will behave differently



    πŸ™

  • default discord avatar
    notchrlast year

    Yo coming back to this thread, nice work guys!

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.