payloadcms/payload · error · Forbidden

error:notAllowedToPerformAction

Error message

error:notAllowedToPerformAction

What it means

Thrown at the start of the token-refresh operation when `args.req.user` is falsy. Refresh requires an existing (expiring) JWT to identify and renew; without one there is nothing to refresh. Surfaces as `Forbidden` (HTTP 403) with the i18n message `error:notAllowedToPerformAction`.

Source

Thrown at packages/payload/src/auth/operations/refresh.ts:57

      collection: args.collection.config,
      operation: 'refresh',
      overrideAccess: false,
    })

    // /////////////////////////////////////
    // Refresh
    // /////////////////////////////////////

    const {
      collection: { config: collectionConfig },
      req,
      req: {
        payload: { config, secret },
      },
    } = args

    if (!args.req.user) {
      throw new Forbidden(args.req.t)
    }

    const pathname = new URL(args.req.url!).pathname

    const isGraphQL = pathname === config.routes.graphQL

    let user = await req.payload.db.findOne<AuthenticatedUser>({
      collection: collectionConfig.slug,
      req,
      where: { id: { equals: args.req.user.id } },
    })

    if (!user) {
      throw new Forbidden(args.req.t)
    }

    const sid = args.req.user._sid

View on GitHub (pinned to 00c58b35c0)

Solutions

  1. Ensure the refresh request carries the `payload-token` cookie / JWT header (`credentials: 'include'`).
  2. Refresh before the access token fully expires (e.g. at ~80% of `tokenExpiration`).
  3. If using the Local API, build `req` from a user: `createLocalReq({ user }, payload)`.
  4. Confirm `config.auth.tokenExpiration` is reasonable and the JWT secret matches the one that signed the token.

Example fix

// before
fetch('/api/users/refresh-token')
// after
fetch('/api/users/refresh-token', { method: 'POST', credentials: 'include' })
Defensive patterns

Strategy: validation

Validate before calling

// Only refresh when a token is present
if (!req.user) {
  // no session to refresh — redirect to login
  return redirectToLogin()
}
await payload.refreshToken({ collection, req })

Type guard

function hasAuthUser(req: PayloadRequest): req is PayloadRequest & { user: AuthUser } {
  return !!req.user
}

Try / catch

try {
  await payload.refreshToken({ collection, req })
} catch (e) {
  if (e instanceof Forbidden) { await redirectToLogin() }
  else throw e
}

Prevention

When it happens

Trigger: A client calls the refresh endpoint (`POST /api/<collection>/refresh-token`) with no token, an already-expired token (middleware rejects → no `req.user`), or a malformed `Authorization` header. Also via Local API `payload.refreshToken({ req })` on an unauthenticated `req`.

Common situations: The access token expired and the refresh logic fires after the cookie is already gone; the frontend's refresh call omits credentials (`fetch(..., { credentials: 'omit' })`); a clock-skew between client and server treats the token as expired before refresh runs.

Related errors


AI-assisted analysis of payloadcms/payload@00c58b35c0 (2026-08-12). Data as JSON: /api/errors/c51c00619dfc409b. Report an issue: GitHub.