medusajs/medusa · error · MedusaError
Forbidden
Error message
Forbidden
What it means
Thrown by the RBAC validate-user-permissions step when the invoking user has no RBAC roles attached, so permission checks cannot proceed. Medusa treats a role-less user as unauthorized for the operation rather than falling back to any default access. It is a FORBIDDEN error from within workflow execution.
Source
Thrown at packages/core/core-flows/src/rbac/steps/validate-user-permissions.ts:56
const { actor_id, actor, policy_ids, actions } = data
if (!policy_ids?.length && !actions?.length) {
return
}
const query = container.resolve(ContainerRegistrationKeys.QUERY)
const { data: users } = await query.graph({
entity: actor ?? "user",
fields: ["rbac_roles.id"],
filters: { id: actor_id },
})
const roleIds: string[] =
users?.[0]?.rbac_roles?.map((r) => r.id).filter(Boolean) ?? []
if (!roleIds.length) {
throw new MedusaError(MedusaError.Types.FORBIDDEN, "Forbidden")
}
let actionsToCheck: { resource: string; operation: string }[] = []
if (policy_ids?.length) {
const { data: targetPolicies } = await query.graph({
entity: "rbac_policy",
fields: ["id", "resource", "operation"],
filters: { id: policy_ids },
})
// A user cannot grant a policy that doesn't exist.
const inexistentPolicies = arrayDifference(
policy_ids,
targetPolicies.map((p) => p.id)
)
if (inexistentPolicies.length) {
throw new MedusaError(View on GitHub (pinned to 5e06e544a2)
Solutions
- Assign at least one RBAC role to the user (via the RBAC admin APIs or seed script) and retry
- Verify the user id passed to the step matches the authenticated actor and that user-roles links exist
- If provisioning flow is custom, ensure role attachment happens at user-creation time
Example fix
// before
await validateUserPermissionsStep({ userId: 'user_123', action: 'assign', policy_ids: ['pol_1'] }) // throws Forbidden
// after
// first attach a role, then run the protected workflow
await link.create('user', 'user_123', 'rbac_role', 'role_admin')
await validateUserPermissionsStep({ userId: 'user_123', action: 'assign', policy_ids: ['pol_1'] }) Defensive patterns
Strategy: validation
Validate before calling
const { data: [user] } = await query.graph({ entity: 'user', filters: { id: userId }, fields: ['rbac_roles.id'] })
if (!user?.rbac_roles?.length) throw new Error('User has no RBAC roles; assign one before this operation') Try / catch
try { await workflow(scope).run({ input }) } catch (e) { if (e instanceof MedusaError && e.type === MedusaError.Types.FORBIDDEN) { /* prompt role assignment / escalate */ } else throw e } Prevention
- Attach at least one RBAC role during user provisioning
- Periodically audit for role-less active users
- Surface role-assignment UI before granting policy management access
When it happens
Trigger: Calling an RBAC-protected workflow/route (e.g. assigning policies to a user) with an authenticated actor that has no rbac_roles linked in the user's record loaded by the step.
Common situations: Newly created admin users that were never assigned an RBAC role; custom auth setups that bypass the RBAC provisioning step; deleting or detaching all roles from a user while they still hold API credentials.
Understand the failure class
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- Policy with id: ${req.params.id} not found
- Policy with id "${req.params.id}" not found
- Role with id: ${req.params.id} not found
- User with id "${userId}" not found
- User with id: ${id} was not found
AI-assisted analysis of medusajs/medusa@5e06e544a2 (2026-08-27).
Data as JSON: /api/errors/b9eb44c458852c2f.
Report an issue: GitHub.