Debugging Access Denied Errors
Relevant source files
The following files were used as context for generating this wiki page:
"Access Denied" errors in @joindeed/prisma-auth occur when the declarative authorization layer determines that a user's context does not satisfy the @Auth requirements defined in the Prisma schema. This system operates at the model level rather than the GraphQL resolver level, using three distinct types of middlewares to enforce field-level, type-level, and list-level constraints.
When debugging these errors, you must verify the intersection of the defined roles, the provided role matchers, and the current user context. The library fails "closed" (whitelist by default for restricted entities), meaning if a role is specified but no matcher grants access, the result is patched to null, "", or [] depending on the field type.
Sources: README.md:1-15, src/index.ts:46-50, src/makeFieldConstraintMiddleware.ts:41-45
Authorization Architecture
The system evaluates access through a waterfall of middlewares applied to the GraphQL schema. Each middleware checks for the presence of an @Auth annotation in the Prisma model or field description.
Authorization Workflow
The following diagram illustrates how a request is processed and where "Access Denied" states originate:
Common Causes of Resource Denial
1. Missing Role Matchers
The system expects a matcher function for every role defined in the schema annotations. If a model mentions a role (e.g., Owner) and the configuration does not provide a matcher for it in globalRoles or rolesPerType, the system throws an error.
- Logic: The middleware calls
readRoles.some(role => ...)and checks ifroleMatcherexists. - Result:
throw new Error(\Role matcher for role "${role.name}" in model "${typeName}" not found`)`.
Sources: src/makeFieldConstraintMiddleware.ts:34-43, src/makeTypeConstraintMiddleware.ts:46-51
2. Failure of Query Constraints
For list queries, the system generates a Prisma where clause. If no role is satisfied, it applies an alwaysFalseCondition which forces the query to return zero results.
| Constraint Type | Behavior | Location |
|---|---|---|
matcher | Returns boolean. Used for individual record access. | Role.matcher |
queryConstraint | Returns a Prisma where object or boolean. Used for lists. | Role.queryConstraint |
Sources: src/getListWhereConstrains.ts:51-60, src/index.ts:10-12
Core Middleware Components
| Component | Responsibility | Relevant File |
|---|---|---|
FieldConstraint | Validates access to individual fields on a returned object. | src/makeFieldConstraintMiddleware.ts |
TypeConstraint | Validates 1-to-1 relation fields when the target type has @Auth. | src/makeTypeConstraintMiddleware.ts |
ListConstraint | Injects where clauses into Prisma queries via withAuth. | src/makeListConstraintMiddleware.ts |
Field-Level Denial Logic
If a field is annotated with @Auth(read:[RoleA]), the makeFieldConstraintMiddleware evaluates the matcher against the parent object. If granted is false, it returns a "patched" response.
// Example from src/makeFieldConstraintMiddleware.ts
if (readRoles.length > 0) {
const granted = readRoles.some((role) => {
const roleMatcher = rolesPerType?.[typeName]?.[role.name]?.matcher || globalRoles?.[role.name]?.matcher;
return roleMatcher(context, parent, role.args);
});
if (!granted) {
return patchResponse(info.returnType);
}
}
Sources: src/makeFieldConstraintMiddleware.ts:31-46
List and Relation Debugging
List denials are harder to debug because they do not throw errors; they simply return empty arrays. This happens inside getListWhereConstrains.ts. The system combines all role queryConstraints using an OR block inside an AND block.
Sources: src/getListWhereConstrains.ts:25-63
The withAuth Helper
Access is often denied if context.withAuth is not applied to the Prisma call. Without it, the ListConstraintMiddleware cannot inject the necessary where and select clauses.
- Requirement: You must wrap your Prisma query:
context.prisma.user.findMany(context.withAuth({ where: ... })). - Behavior:
withAuthmerges the authorizationselectandwhereconstraints into the user's query parameters.
Sources: src/makeListConstraintMiddleware.ts:18-45, README.md:95-105
Debugging Checklist
- Check Annotations: Ensure the field or model has a triple-slash comment (e.g.,
/// @Auth(...)). - Verify Matcher Dependencies: If a matcher relies on
record.userId, ensurematcherDependenciesSelectis configured to select that field. If the field is missing from the record, the matcher may return false incorrectly. - Inspect
withAuthUsage: Ensure the Prisma query in the resolver actually usescontext.withAuth(query). - Validate Role Arguments: If roles use arguments (e.g.,
Owner(privileges: [x])), verify that theroleArgsobject passed to the matcher contains the expected values. - Examine Introspection: The library skips all logic for GraphQL introspection fields (fields starting with
__). Access denied errors on these are likely caused by other schema-level tools.
Sources: src/getMatcherDependenciesSelect.ts:11-30, src/makeListConstraintMiddleware.ts:24-30, src/descriptionToRoles.ts:22-28
Summary
Debugging access denied errors in @joindeed/prisma-auth requires verifying that @Auth annotations correctly map to implemented matchers in the Configuration. When access is missing, the system silently patches responses to null or empty values via patchResponse or injects an alwaysFalseCondition into list queries. Developers should focus on the matcherDependenciesSelect to ensure necessary data is available for evaluation and confirm that withAuth is consistently applied to all Prisma interactions.