stalwartlabs/stalwart · error · ScimError
Forbidden
Error message
Forbidden
What it means
SCIM protocol constructor `Error::forbidden(detail)` builds a SCIM Error with HTTP status 403 and a human-readable detail. It means authentication succeeded but the authenticated principal is not allowed to perform the operation. Returned by server code via this constructor; the detail string explains what was denied.
Source
Thrown at crates/scim-proto/src/message/error.rs:167
pub fn invalid_count(detail: impl Into<Cow<'static, str>>) -> Self {
Self::bad_request(ScimType::InvalidCount, detail)
}
pub fn uniqueness(detail: impl Into<Cow<'static, str>>) -> Self {
Error {
status: 409,
scim_type: Some(ScimType::Uniqueness),
detail: Some(detail.into()),
}
}
pub fn unauthorized() -> Self {
Error::new(401)
}
pub fn forbidden(detail: impl Into<Cow<'static, str>>) -> Self {
Error::new(403).with_detail(detail)
}
pub fn not_found() -> Self {
Error::new(404)
}
pub fn conflict(detail: impl Into<Cow<'static, str>>) -> Self {
Error::new(409).with_detail(detail)
}
pub fn precondition_failed() -> Self {
Error::new(412)
}
pub fn max_operations_exceeded(max_operations: usize) -> Self {
Error::new(413).with_detail(format!(
"The number of operations in the bulk request exceeds the maxOperations ({max_operations})."
))View on GitHub (pinned to e962003857)
Solutions
- Read the `detail` field in the SCIM error body — it names the denied operation or resource.
- Request the missing scope/role via the OAuth2 flow and retry with a new token.
- Confirm the target resource belongs to the caller's tenant/organization.
- If you are the server author, ensure 403 details avoid leaking information about resources the caller should not know exist.
Example fix
// before
let token = get_token(&client_id, scopes=["scim:read"]);
client.put("/scim/v2/Users/2819", body).bearer_auth(token) // 403
// after
let token = get_token(&client_id, scopes=["scim:read", "scim:write"]);
client.put("/scim/v2/Users/2819", body).bearer_auth(token) Defensive patterns
Strategy: fallback
Validate before calling
// check required scope client-side before a write request
fn can_write(scopes: &[String]) -> bool {
scopes.iter().any(|s| s == "scim:write" || s == "scim:admin")
} Try / catch
let resp = client.put(&url).bearer_auth(&token).json(&body).send().await?;
if resp.status() == StatusCode::FORBIDDEN {
let scim_err: ScimError = resp.json().await?;
anyhow::bail!("SCIM forbidden: {:?}", scim_err.detail); // surface detail, don't retry blindly
} Prevention
- Request all needed scopes (read + write) up front when provisioning tokens.
- Read the SCIM error detail — it usually names the denied resource or operation.
- Do not auto-retry 403s; they require a permission change, not a retry.
- In multi-tenant systems, verify the target resource ID belongs to the caller's tenant first.
When it happens
Trigger: Server code calls `Error::forbidden(detail)` (i.e. `Error::new(403).with_detail(detail)`) when a request targets a resource or operation outside the caller's granted scopes/permissions — e.g. reading another tenant's Users, writing with a read-only token.
Common situations: Access token lacking required scope (e.g. scim:write) for a PUT/POST/PATCH; non-admin user hitting admin-only SCIM endpoints; cross-tenant access attempts in multi-tenant deployments; RBAC policy changes revoking previously granted roles.
Understand the failure class
Background: Permission denied / not authorized / 403 Forbidden: access-control rejections when the caller lacks the required role, grant, or ownership — this error's family across 18 libraries.
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- Unauthorized
- Not Found
- Conflict
- Precondition Failed
- The number of operations in the bulk request exceeds the max
AI-assisted analysis of stalwartlabs/stalwart@e962003857 (2026-09-06).
Data as JSON: /api/errors/8881a7b733d70fd4.
Report an issue: GitHub.