RFC: Authorization Layer Overhaul with CASL
RFC: Authorization Layer Overhaul with CASL
- Authors: Prathik Shetty(@pshettydev), Shaik Noorullah(@pnow-devsupreme)
- Date: 2026-01-14
- Status: Draft
Abstract
This RFC proposes a complete overhaul of the current authorization layer in the PNow ATS v2 application. The objective is to replace the existing temporary solution with a robust, scalable, and user-customizable Policy-Based Access Control (PBAC) / Attribute-Based Access Control (ABAC) system.
After evaluating options, this proposal recommends adopting CASL (Code Access Security Library) over Cerbos. CASL's isomorphic nature, deep integration with Prisma (our ORM), and ability to handle dynamic, database-stored rules make it the superior choice for our specific requirement of allowing end-users to customize permission policies.
Motivation
The current authorization implementation is a "hot fix" that is rigid, difficult to maintain, and does not function as intended. It fails to meet the core business requirement: User-Defined Policy Customization.
We need a system that supports:
- Granular Permissions: Control access not just by role, but by specific attributes (e.g., "Department Lead can only edit Candidates in their own department").
- User Customizability: Tenants/Admins must be able to define and modify access policies through the UI, without developer intervention or code deployment.
- Performance: Authorization logic must apply at the database query level to prevent over-fetching sensitive data.
- Consistency: Access rules must be consistent across the Backend (API security) and Frontend (UI visibility).
Approaches
We evaluated two primary candidates for the authorization engine: Cerbos and CASL.
Option 1: Cerbos
Cerbos is a self-hosted, open-source Policy Decision Point (PDP) that runs as a separate service (sidecar or standalone).
- Pros:
- Decouples authorization logic entirely from application code.
- Language-agnostic (great for polyglot microservices).
- "GitOps" friendly: Policies are typically defined in YAML and version controlled.
- Cons:
- Infrastructure Overhead: Requires deploying and maintaining a separate service.
- Dynamic Policy Friction: While Cerbos supports dynamic loading, it is optimized for static policies managed by developers/ops. Bridging the gap between a "User UI" for permissions and Cerbos YAML policies would require building a complex translation and storage layer.
- Database Integration: While it has query plan adapters, integrating them into our NestJS/Prisma stack adds another layer of complexity compared to a native library.
Option 2: CASL (Recommended)
CASL is an isomorphic authorization JavaScript/TypeScript library.
- Pros:
- Native TypeScript Integration: Fits perfectly into our NestJS (Backend) + Next.js (Frontend) monorepo.
- Dynamic by Design: CASL is built to hydrate
Abilityinstances from JSON objects. This aligns perfectly with our database schema whereGroupPolicyhas aconfigurationJSON field. - Prisma Integration: The
@casl/prismapackage allows us to convert permission rules directly into Prismawhereclauses. This enables "Safe Queries" (e.g.,prisma.candidate.findMany({ where: accessibleBy(ability).Candidate })), ensuring we never fetch unauthorized data. - Isomorphic: We can share the exact same subject definitions and rule logic between backend and frontend.
- No Infrastructure Overhead: Runs in-process as a standard library.
- Cons:
- Tightly coupled to the Node.js ecosystem (not an issue for us as we are fully Node/TS).
Proposal
We propose implementing the authorization layer using CASL. This approach leverages our existing architecture and meets the requirement for user-customizable policies.
Core Architecture
-
Policy Storage:
- We will utilize the existing
GroupPolicymodel in our Prisma schema. - The
configurationfield (Json) will store CASL-compatible rules (e.g.,[{ "action": "read", "subject": "Candidate", "conditions": { "ownerId": "${user.id}" } }]). - Tenants can create/edit these JSON rules via a UI Builder, effectively creating their own policies.
- We will utilize the existing
-
Backend Implementation (NestJS):
- Factory: A
CaslAbilityFactorywill be created. On every request, it will:- Fetch the user's
UserGroupand relatedGroupPolicyrecords. - Merge the raw JSON rules.
- Interpolate variables (e.g., replace
${user.id}with the actual ID). - Return a user-specific
Abilityinstance.
- Fetch the user's
- Guards: A global
PoliciesGuardwill intercept requests and check permissions using theAbility. - Decorators:
@CheckPolicies()decorators will be used on Controllers to define required permissions. - Data Access: We will use
@casl/prismain our Services to automatically filter database queries based on the user's ability.
- Factory: A
-
Frontend Implementation (Next.js):
- Synchronization: On login/bootstrap, the backend will send the computed list of JSON rules to the frontend.
- Context: A React Context (
AbilityContext) will hold the CASLAbilityinstance. - Components: We will use CASL's
<Can>component (or a custom wrapper) to conditionally render UI elements (e.g., hiding the "Delete" button if the user lacks permission).
Features to be Implemented
- Dynamic Policy Engine:
- System capability to load permissions from the database at runtime.
- Support for "Standard/System" policies (seeded) and "Custom" policies (user-created).
- Visual Policy Builder:
- A frontend UI that allows admins to construct policies using dropdowns (Select Subject -> Select Action -> Add Conditions), which saves as valid CASL JSON.
- Secure Querying:
- Implementation of
accessibleByin Repositories/Services to ensure no data leaks occur at the DB level.
- Implementation of
- Field-Level Security:
- Utilize CASL's field limitations to prevent users from reading/updating sensitive fields (e.g.,
salary) even if they have access to the resource.
- Utilize CASL's field limitations to prevent users from reading/updating sensitive fields (e.g.,
- Multi-Tenancy Isolation:
- Ensure all policy evaluations are strictly scoped to the user's
tenant_id.
- Ensure all policy evaluations are strictly scoped to the user's
Why CASL wins for PNow ATS v2
| Feature | Cerbos | CASL | Verdict |
|---|---|---|---|
| User-Customizable Rules | Complex (Requires translation layer) | Native (JSON storage) | CASL |
| Performance | Network hop required | In-memory | CASL |
| DB Integration | Query Plan Adapter | @casl/prisma Native | CASL |
| Frontend Reuse | Separate logic needed | Isomorphic (Shared code) | CASL |
| Maintenance | High (New Service) | Low (Library Update) | CASL |
By choosing CASL, we align with our stack (NestJS/Prisma/Next.js), simplify our deployment, and directly address the "user-customizable" requirement by storing policies as data, not code.