Diagnosing FaunaDB 403 Permission Denied Errors in Role-Based Access Control
A diagnostic guide for FaunaDB 403 permission denied errors, covering role membership, privileges, predicates, and user-defined function context.
05 Jun 2026, 16:54 UTC

Recognizable Condition
An authenticated FaunaDB client receives a 403 Forbidden or permission denied error when reading a collection, calling a user-defined function, or executing an index-backed query. The identical query succeeds when run with an admin key. This pattern means authentication passed but authorization failed: FaunaDB recognized the secret or token, yet the role-based access control (RBAC) system refused the operation.
Distinguishing 403 From 401
A 401 Unauthorized response indicates that FaunaDB could not authenticate the request, usually because the secret is invalid, expired, or malformed. A 403 Forbidden response means the credential is valid, but the identity lacks the necessary role, privilege, or predicate result to perform the action. When you see 403, focus your diagnosis on roles, privileges, and predicates rather than on the secret itself.
Common Causes and Diagnostic Table
Use the following table to map the symptom to the most likely cause and the object you need to inspect.
| Symptom | Likely Cause | What to Inspect |
|---|---|---|
| 403 on every resource, but admin key works | Identity has no role membership | Identity document roles field |
| 403 on one collection or function | Role lacks a privilege for that resource and action | Role privileges definition |
| 403 on specific documents only | Role predicate evaluates false for the current identity or document | Role predicate function |
| 403 when calling a user-defined function | Function role context does not include the underlying resources | UDF role field or caller role |
| 403 on an index-backed query | Index or referenced function is not covered by role privileges | Privileges for the index and target collection |
Ordered Checks
Perform these checks in order. Each step isolates a different layer of the authorization chain.
1. Verify the Secret Is Active
Run this check in the Fauna dashboard or with an admin key in the Fauna shell. Confirm that the secret or token used by the client is present, active, and not expired. If the credential itself is revoked, the client will receive a 401 rather than a 403, but an expired token can sometimes surface as an authorization failure in certain drivers.
Expected check: The secret appears in the dashboard security list with an active status.
2. Confirm Identity Role Membership
Read the identity document that the token represents. In many FaunaDB schemas, this is a document in an accounts or users collection. The document should contain a roles field listing the roles assigned to that identity.
# Run with an admin key in the Fauna shell or driver
Get(Ref(Collection("<identity-collection>"), "<identity-id>"))
Expected check: The returned document includes the expected role reference in its roles array.
Risk: If the identity document does not exist or the roles field is empty, every non-admin query from that client will fail with 403.
3. Inspect Role Privileges
Retrieve the role definition and examine its privileges array. Each privilege entry pairs a resource, such as a collection, index, or user-defined function, with a list of allowed actions, such as read, write, or call.
For example, a role intended to allow reading an orders collection might be defined conceptually as:
{
"name": "order_reader",
"privileges": [
{
"resource": "orders",
"actions": ["read"]
}
]
}
In practice, you would create or update this role through FQL or the dashboard. The exact syntax varies by FaunaDB version and driver, so verify against your environment.
Expected check: The role contains a privilege entry whose resource matches the target of the failing query and whose actions include the operation being attempted.
4. Evaluate the Role Predicate
If the privilege exists but the query still fails, the role may include a predicate. A predicate is an FQL lambda that FaunaDB evaluates at query time. It receives the identity and the target document as arguments and must return true for the operation to proceed.
Consider a predicate that restricts access to orders belonging to the current customer:
Lambda(
["account", "order"],
Equals(
Var("account"),
Select(["customerRef"], Var("order"))
)
)
Expected check: The predicate returns true for the specific identity and document involved in the failing request.
Risk: Predicates often reference document fields that may not exist. A missing field can cause the predicate to fail silently, producing a 403 without a schema error.
5. Check User-Defined Function Role Context
When a client calls a user-defined function (UDF), FaunaDB evaluates privileges against the UDF's own role if one is set; otherwise, it uses the caller's role. If the UDF reads or writes resources that the caller's role does not cover, the call fails with 403 even though the caller has permission to invoke the function itself.
Expected check: The UDF either has no role and the caller's role covers all underlying resources, or the UDF has a role whose privileges include those resources.
Fixes Tied to Findings
Apply the fix that matches the check that failed.
- Missing role on identity: Add the role reference to the identity document's
rolesfield. For example, update the document so that itsrolesarray includes the reference to the role you inspected in check 3. - Missing privilege: Update the role to include the correct resource and action. If the query reads through an index, ensure the role also grants
readon the index itself, not only on the underlying collection. - Predicate returns false: Rewrite the predicate so that it returns
truefor the intended condition. Use defensive functions such asSelectwith default values orContainsPathto handle documents where expected fields are absent. - UDF role mismatch: Either grant the caller's role privileges on every resource the UDF touches, or assign the UDF a dedicated role that has those privileges. Do not rely on the caller's role to implicitly cover internal operations.
- Stale token: After changing roles or membership, reissue the token or have the client re-authenticate. Existing tokens may continue to reflect the role set that was active when they were created.
When to Escalate
Escalate to Fauna support when the following conditions are all true:
- The secret or token is valid and active.
- The identity document contains the expected role.
- The role definition includes the required privilege and a predicate that appears correct.
- The error persists across multiple identities and multiple attempts.
When you escalate, include the exact FQL query, the complete role JSON, the identity document, and the precise error message and status code. This allows support to reproduce the authorization decision without guessing.
Limitations and Practical Verification
FaunaDB role changes may not be immediately visible to every edge node. After updating a role, allow a short propagation delay before retesting. Predicate behavior can also differ between FaunaDB regions and between cloud and self-managed deployments, so verify against the target environment.
Never use an admin key in a client-side or frontend application. Admin keys bypass all role checks and will mask authorization bugs that only appear under a restricted secret.
To verify a fix, run a minimal query that touches only the suspected resource. For example, attempt to read a single document by reference using the client secret. If the minimal read succeeds, the privilege and predicate are correct, and you should investigate indirect access through indexes or user-defined functions. If it still returns 403, the problem remains at the role, privilege, or predicate level.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.