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
- Ensure the refresh request carries the `payload-token` cookie / JWT header (`credentials: 'include'`).
- Refresh before the access token fully expires (e.g. at ~80% of `tokenExpiration`).
- If using the Local API, build `req` from a user: `createLocalReq({ user }, payload)`.
- 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
- Refresh proactively before the access token expires.
- Send cookies with `credentials: 'include'`.
- Guard refresh logic behind a `req.user` check.
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
- No User
- Cannot refresh token: user not authenticated
- Migration ${migration.name} not found locally.
- Username or email is required
- validation:required
AI-assisted analysis of payloadcms/payload@00c58b35c0 (2026-08-12).
Data as JSON: /api/errors/c51c00619dfc409b.
Report an issue: GitHub.