Skip to main content

Insufficient permissions

The 403 that will not tell you which permission was missing — how to work it out, plus two refusals that are wiring gaps you cannot fix.

Written for whoever runs IT9 min readUpdated

Somebody signed in successfully and then hit a wall. This article covers the five refusals that look alike and mean different things, how to find out which permission a call actually needed, and two cases where the honest answer is that the refusal is a gap in our wiring and there is nothing on your side to change.

The refusals, and what each one means

Authorisation refusals with their status codes. The last two are not misconfiguration; read their sections before acting.
BodyStatusMeaning
{"error":"missing JWT claims"}401No valid session at all. The token is absent, malformed or expired. This is an authentication problem, not a permission one — start at Signed out unexpectedly.
{"error":"insufficient permissions"}403You have a session, we know which organisation, and your role does not hold the permission this route requires. This is the ordinary case and the fix is a role change.
{"error":"permission check failed"}500We could not evaluate the check. Ours, not yours. Retry, then tell us.
{"error":"token carries no organization"}403Your session names no organisation. Usually because the person belongs to more than one — a known limitation, described below.
{"error":"organization mismatch"}403The organisation in the address is not the one your credential belongs to. A pasted link for the wrong tenant, or the multi-organisation case again.
step-up authentication required403A second, layered check wants a fresh re-authentication. The console does not send the signal it looks for, so this is a wiring gap. See its own section.

A small operational note if you are automating: insufficient permissions and its siblings are written with Go's plain error helper, so the body is JSON but the Content-Type header says text/plain. A strict client that trusts the header will not parse it. Match on the status code and the substring rather than on a parsed object.

If the caller is an API token rather than a person, the vocabulary is different again and the distinctions are useful: 401 {"error":"invalid api key"}, {"error":"api key revoked"} and {"error":"api key expired"} tell you which of the three happened, and 403 {"error":"insufficient api key scope"} is the token-level equivalent of a role denial. The plain authentication failures — missing authorization header, invalid authorization format, invalid token — are all 401 and mean the request never reached an authorisation check at all.

Finding out which permission the call needed

The 403 does not name the missing permission. That is a real shortcoming and we are not going to pretend otherwise — a message reading insufficient permissions: this route requires siem:alerts.write would resolve most of these tickets without a support call. Until it exists, here is how to work it out in about two minutes.

  1. List every permission that exists

    GET /api/v1/permissions returns the whole catalogue. Permissions are named resource plus action — for example users and read, or iam:auth_policies and write. The catalogue is large; roughly a hundred entries, grown by migration over time.

  2. List what the person's role actually holds

    GET /api/v1/roles/{roleID}/permissions. Compare that against the catalogue. In practice you are looking for the resource that matches the area of the console they were in — users, roles, organizations, sso, iam:network_zones, iam:auth_policies, iam:federation, integration:webhooks, pam:*, siem:*, xdr:*.

  3. Match the verb to the action

    Reading a page is read. Creating or editing is write. Removing is delete. If they could see the page and not save, they hold read and not write.

  4. Grant it, or move them to a role that has it

    POST /api/v1/roles/{roleID}/permissions adds one to a custom role. Prefer moving the person to an existing role over widening a role that other people share — widening a role is invisible to everybody else who holds it.

Two further consequences of the model are worth knowing. A membership carries exactly one role, so permissions are not additive across roles — you cannot give somebody a second role to top them up. And a person with no membership in an organisation gets a refusal for everything in it, which is the single most confusing failure mode in the product, because they can sign in perfectly and then be refused every page. If a brand-new user is refused everywhere, check that they have a membership before you check their role. Users created by our bulk import have none at all — see A bulk import was rejected.

`token carries no organization`

Some routes are not organisation-scoped in their URL and take the organisation from the session token instead — Sentinel-Q, billing and the GDPR subject-request queue among them. If the token carries no organisation, those routes fail closed with 403 and this message.

Failing closed is correct and it is a fix, not a bug: this used to be a silent pass, which meant anybody with a session could reach several of those routes regardless of their role. But it does surface a real limitation, because there is a legitimate way to end up with no organisation in your token: belonging to more than one. There is no organisation selector to resolve it with. That case, and what to do today, are covered in Signed out unexpectedly.

The step-up wall — a wiring gap, not a setting

There is a second, more elaborate authorisation path in the product. It layers device trust, network trust, region, incident state and a step-up requirement on top of the ordinary permission check. Its decisions are returned as plain sentences, and two of them are the ones people arrive here having seen:

Decision reasons from the layered authorisation path
insufficient permissions
step-up authentication required
action requires elevated delegated role
organization membership required
organization not found
request region does not match the serving region
tenant placement metadata is missing for this serving path
tenant is not assigned to this serving cell
request cell does not match tenant placement
request placement version does not match tenant placement

In that model, users.write, roles.write and organizations.write always require a step-up — a re-authentication performed within the last fifteen minutes — and api_keys.write, members.write and sso.write require one conditionally, when the device is low-trust, the network is untrusted, or an incident is active. action requires elevated delegated role fires when members.write, roles.write or sso.write is attempted by somebody who is not an administrator or a delegated administrator.

The ordinary REST surface — everything you reach from the console — is guarded by the simple permission check described in the first half of this article, not by this layer. If you are seeing insufficient permissions you are in the ordinary path and the fix is a role. If you are seeing step-up authentication required, you are in the layered path and you are stuck.

Delegated administration is not usable yet

action requires elevated delegated role names a role — delegated_admin — that the product intends to support: an administrator whose powers are scoped to a subset of the directory, so that a regional manager can reset passwords for their own region and nobody else's.

The scoping mechanism is not implemented. The reader that resolves a delegated grant is a stub that always answers no, there are no routes for creating a grant, and the function that would create one has no caller. So a delegated permission check can only ever fail. If you are trying to build a scoped-administrator model today, use custom roles with a narrow permission set instead, and accept that they are organisation-wide rather than scoped to a subset — see Custom roles and least privilege and Delegated administration.

Which of these you can actually fix

A blunt summary. Two of these five are ours, and a reader who cannot fix something should stop trying.
RefusalYours or ours
insufficient permissionsYours. Grant the permission or change the role.
token carries no organizationMixed. The multi-organisation cause is a known limitation of ours; the workaround is yours.
permission check failedOurs. A fault, not a verdict.
step-up authentication requiredOurs. A wiring gap. Nothing you can configure.
action requires elevated delegated roleOurs. The delegated-administration mechanism is not implemented.

Was this article wrong?

If a procedure here does not match what you see, or a limit we described has changed, tell us and we will fix the page. Email us about this article, or see how to get help if you need an answer rather than a correction.

Everything in troubleshooting