What this is about
Three codes look alike, but they call for completely different reactions from the client:
- 401: Your token is no good. Get a new one.
- 403: You are known, but you may not do this. Or you did not send a token at all.
- 404: The object does not exist for you. It may exist, but you may not see it.
The three compared
- only comes from the filter chain
- token expired, broken or wrongly signed
- no body, header
WWW-Authenticate: Bearer error="invalid_token" - CDMS never saw the request
- no token: filter chain, body without
messageKey - tenant refused: filter chain,
errorwithcias.authentication.… - role missing: CDMS,
missing-permission|<role> - applies equally to all objects of a model
- object missing or invisible:
not-found…,missing-object|… - singleton not created yet:
no-data-exists|use-create - path unknown: no
messageKey - never reveals whether someone else's object exists
The decision path
flowchart TB
S{"Status?"} -->|401| R["Renew the token,<br/>retry the request once"]
R --> R2{"401 again?"}
R2 -->|yes| L["log in again"]
S -->|403| K{"Body?"}
K -->|"no messageKey,<br/>no error"| H["check the Authorization header,<br/>log in"]
K -->|"error: cias.authentication.…"| M["check the tenant:<br/>organization, tenant header"]
K -->|"messageKey"| P["role missing:<br/>hide the action, show a hint"]
S -->|404| N{"messageKey?"}
N -->|"no"| U["wrong path:<br/>check model path and endpoint"]
N -->|"no-data-exists…"| C["create the singleton"]
N -->|"not-found, missing-object"| G["treat as deleted:<br/>remove from the list, go back"]
In which order CDMS checks
If both apply, i.e. the role is missing and the object is invisible: which code comes then? That depends on the operation.
When: POST /read/{id}, GET /read/{id}
-
1CDMSchecks the read role
-
2CDMSrole missing → 403
missing-permission|<read role> -
3CDMS→Databasereads the row with all filters, no row → 404
not-found
Result: Without the read role you get 403, whether the object exists or not.
When: PUT, PATCH, DELETE, POST /{id}/rollback/{revision}
-
1CDMS→Databasecounts the row with the same filters as when reading
-
2CDMSinvisible or not present → 404
not-found|<Dto>|<id> -
3CDMSvisible, role missing → 403
Result: When writing, visibility comes first. So even a missing permission reveals nothing about other people's objects.
When: The installation runs with STRICT_MODE=false.
Reading by id without the read role then returns 404 not-found instead of 403. Writing stays at 403, just with a different key, such as missing-update-role.
Result: See Strict mode.
The full table per operation is in Why invisible objects return 404.
Client reaction per code
| Status | Body | Reaction |
|---|---|---|
| 401 | empty | Renew the token, retry the request once. If renewing fails or 401 comes again: log in again. |
| 403 | empty or without messageKey | The request had no token. Check the header Authorization: Bearer <token>, otherwise log in. |
| 403 | error with cias.authentication.… (for a download with access_token in the messageKey) | Tenant cannot be determined, is not served or is missing. Do not retry, check the organization or the tenant selection. |
| 403 | missing-permission|<role> and others | Role missing. Hide the action or show a hint. Do not retry. |
| 404 | not-found…, missing-object|… | The object is gone or invisible. Remove it from the view, go back to the list. Do not retry. |
| 404 | no-data-exists|use-create | The singleton does not exist yet. Create it with create. |
| 404 | without messageKey | The path does not exist. Check model path and endpoint in the code. |
Pitfalls
Where to go next
- All codes at a glance: Map of status codes
- How the error body is built: The error format
- Which role an operation requires: Model roles
- How the tenant is determined: Where the tenant of a request comes from
- Renewing the token: Renew the token