payload icon indicating copy to clipboard operation
payload copied to clipboard

Potential Bug: Refresh token API endpoint returns 500 with cast error

Open paper-krane opened this issue 5 months ago • 1 comments

Describe the Bug

When attempting to use the built in auth 'refresh token', if your auth collection has a populated relationship field, the refresh response will be a server 500 error with a cast error. I am using the latest Payload and just tested on a fresh install. Also using MongoDB as the database.

I should also add I have tested on both Windows and MacOS.

Link to the code that reproduces this issue

https://github.com/paper-krane/test-refresh-token

Reproduction Steps

  1. Launch Payload Server
  2. Create a user at the 'users' collection endpoint. I have both users and admins in this app.
  3. Create a product (easiest to do so from admin).
  4. Populate the users 'productOwned' field with the product just created.
  5. Login as the user (not admin). I have been using Postman to test this particular bug out.
  6. Try to refresh token.
  7. Error should be returned.

Which area(s) are affected? (Select all that apply)

Not sure

Environment Info

Binaries:
  Node: 24.2.0
  npm: N/A
  Yarn: N/A
  pnpm: N/A
Relevant Packages:
  payload: 3.47.0
  next: 15.3.2
  @payloadcms/db-mongodb: 3.47.0
  @payloadcms/email-nodemailer: 3.47.0
  @payloadcms/graphql: 3.47.0
  @payloadcms/next/utilities: 3.47.0
  @payloadcms/payload-cloud: 3.47.0
  @payloadcms/richtext-lexical: 3.47.0
  @payloadcms/translations: 3.47.0
  @payloadcms/ui/shared: 3.47.0
  react: 19.1.0
  react-dom: 19.1.0
Operating System:
  Platform: win32
  Arch: x64
  Version: Windows 11 Home
  Available memory (MB): 65430
  Available CPU cores: 64

paper-krane avatar Jul 16 '25 03:07 paper-krane

I can confirm this also occurs with Postgres.
I don't love this solution but for now I was able to continue using the standard refresh token by adding the following hook to the authorized collection.

hooks: {
    afterRead: [
      async ({ doc }) => {
        // Normalize relationship fields to only contain IDs after reading from database
        // This ensures that when the user is read and then passed to db.updateOne() in refresh,
        // the relationships are IDs and not populated objects

        if (doc.productOwned) {
          doc.productOwned = doc.productOwned.map((product: any) =>
            typeof product === 'number' ? product : product.id,
          )
        }
        return doc
      },
    ],
    

rymichal avatar Oct 17 '25 17:10 rymichal