Model-Level Authorization
Relevant source files
The following files were used as context for generating this wiki page:
Model-Level Authorization provides a declarative authorization layer for Prisma applications, specifically tailored for GraphQL APIs. It replaces imperative authorization logic within resolvers with annotations defined directly in the Prisma schema. This approach ensures that security rules persist across all relations and nested queries, preventing accidental data exposure when using tools that automatically expose the schema.
Sources: README.md:3-15, src/index.ts:39-43
Core Architecture
The system operates as a set of GraphQL middlewares that intercept requests and results to apply authorization rules. It categorizes authorization into three primary levels: fields, types (1-to-1 relations), and lists.
Authorization Workflow
The following diagram illustrates how the middleware stack processes a GraphQL request:
The middleware stack validates access rights based on the @Auth annotations found in the Prisma model descriptions.
Sources: src/index.ts:39-43, src/makeFieldConstraintMiddleware.ts:20-25, src/makeTypeConstraintMiddleware.ts:37-41
Annotation Syntax
Authorization rules are defined using triple-slash comments (///) in the schema.prisma file. These annotations are parsed to determine which roles have access to specific models or fields.
| Annotation Level | Syntax Example | Description |
|---|---|---|
| Model Level | /// @Auth(read: [Owner, Admin]) | Restricts access to the entire model. |
| Field Level | /// @Auth(read: [Admin]) | Restricts access to a specific field within a model. |
| Role Arguments | /// @Auth(read: [Owner(priv: [x])]) | Passes custom arguments to the role matcher function. |
Sources: README.md:23-40, src/descriptionToRoles.ts:18-24
Middleware Components
Field Constraint Middleware
The makeFieldConstraintMiddleware enforces authorization on individual model fields. If a field has an @Auth annotation, the middleware executes a role matcher against the parent object. If no role is granted, the middleware returns a patched response (e.g., null or an empty string) instead of the actual data.
Sources: src/makeFieldConstraintMiddleware.ts:17-48
Type Constraint Middleware
The makeTypeConstraintMiddleware secures 1-to-1 relations. It extracts the non-list return type of a field and checks the model-level @Auth constraints for that type. This prevents unauthorized access when navigating from one entity to a related single entity.
Sources: src/makeTypeConstraintMiddleware.ts:35-68
List Constraint Middleware
The makeListConstraintMiddleware manages list queries and nested relation queries. It utilizes a withAuth helper to overlay generated Prisma where and select clauses onto the user's query parameters.
Sources: src/makeListConstraintMiddleware.ts:25-72
Data Selection and Constraints
Query Constraint Generation
For list operations, the system generates a Prisma where clause. It combines all applicable role constraints using an OR logic, then wraps them in an AND block to ensure they are enforced alongside user-provided filters.
- Always True Condition: Used when a role (like Admin) has unconditional access.
- Always False Condition: Used when no roles are granted, effectively short-circuiting the query to return no results.
Sources: src/getListWhereConstrains.ts:9-62
Matcher Dependencies
Role matchers often require specific fields from the database record (e.g., an ownerId to check against context.userId). The getMatcherDependenciesSelect function identifies these requirements and ensures they are included in the Prisma select statement, even if the GraphQL client did not request them.
Configuration
The system is configured via a Configuration object passed to makeAuthorizationMiddlewares.
| Option | Type | Description |
|---|---|---|
globalRoles | Record<string, Role> | Roles applicable across all models. |
rolesPerType | RolesPerType | Model-specific overrides for role matchers and constraints. |
defaultFields | Record<string, Function> | Automatically includes specific fields when certain types are queried. |
Sources: src/index.ts:15-32, README.md:46-77
Implementation Example: withAuth
The withAuth function is the primary mechanism for applying constraints to Prisma calls within resolvers.
// Usage in a resolver
resolve: async (parent, args, context) => {
return context.prisma.purchases.findMany(
context.withAuth({
where: { status: 'COMPLETED' }
})
)
}
When withAuth is called, it merges the developer's query with the security where clause generated by getListWhereConstrains and the select clause generated by PrismaSelect.
Sources: src/makeListConstraintMiddleware.ts:41-60, README.md:79-85
Summary
Model-Level Authorization centralizes security logic within the data model. By leveraging GraphQL middleware and Prisma's query engine, it provides a robust mechanism to filter lists, restrict field access, and secure relations based on declarative @Auth annotations. This architecture reduces the risk of security vulnerabilities in complex GraphQL schemas.