CASL Decision
Why Trovella chose CASL with hybrid RBAC+ABAC over Casbin, Permit.io, Oso, and custom JWT claims (ADR-004).
This page summarizes ADR-004. The full decision record is in docs/architecture/decisions/004-authorization-casl-rbac-abac.md.
Decision
Library: CASL (isomorphic JavaScript, MIT licensed, ~12KB, $0 at any scale)
Model: Hybrid RBAC+ABAC -- roles decide most access, attribute checks decide sensitive operations
Roles: Owner, Admin, Member (per organization)
Why CASL
The authorization system needed to:
- Work identically across Next.js server, browser, and future React Native -- one permission definition, not separate implementations per platform
- Layer on top of PostgreSQL RLS as an additional defense, not a replacement
- Start simple (RBAC with 3 roles) and evolve to handle attribute-based conditions without a rewrite
- Be declarative and centralized -- all permissions in one file, auditable, AI-agent-friendly
CASL scored highest on solopreneur-weighted criteria:
| Criterion | CASL | Casbin | Permit.io | Oso | Custom JWT |
|---|---|---|---|---|---|
| Isomorphic JS | Yes | No | No | No | Yes |
| $0 cost | Yes | Yes | No ($100-500+/mo) | Partial | Yes |
| React integration | @casl/react <Can> | None | None | None | Manual |
| Declarative rules | Single file | CONF DSL | Dashboard | Polar DSL | Scattered checks |
| Maintenance burden | Low | Medium | Low (managed) | Medium | High |
| Overall score | 9/10 | 6/10 | 7/10 | 6/10 | 5/10 |
Alternatives Rejected
Casbin -- No native React integration, not truly isomorphic (browser library has different API from node-casbin), CONF policy DSL is unnecessary complexity for 3 roles.
Permit.io -- $100-500+/month vendor dependency on the critical path. PDP sidecar containers add infrastructure overhead. Full-featured dashboards are unnecessary for a solo developer.
Oso -- Polar is a new language to learn, no React integration, no isomorphic JavaScript. Would be more competitive for a purely server-rendered application.
Custom JWT claims -- Zero dependencies but permission checks scatter throughout the codebase. Every new feature requires finding and updating scattered role === 'admin' checks. AI agents are especially prone to implementing inconsistent check patterns.
SpiceDB / OpenFGA (ReBAC) -- Overkill for current scope. Requires a separate relationship graph database. Deferred to Phase 3 if complex sharing hierarchies emerge.
Implementation Trade-offs
String subjects instead of typed CASL subjects
CASL's InferSubjects type utility was incompatible with the project's TypeScript configuration when using condition objects. Plain string subjects ("Organization", "Member", "ResearchPlan", etc.) were used instead. This means CASL's MongoDB-style conditions cannot be used on the ability object itself.
ABAC conditions as explicit router checks
Because string subjects do not support CASL condition objects, the hybrid approach splits cleanly:
- CASL handles RBAC -- role-to-permission mapping in
defineAbilityFor() - Router code handles ABAC -- contextual/ownership checks like "admin cannot modify owners" or "member can only delete self"
This separation keeps CASL simple and makes ABAC logic visible in the router where the context is available. See Router Enforcement for examples.
Personal org restrictions via cannot() overrides
Regardless of role, personal organizations always deny create Member and manage Invitation. This is enforced via cannot() overrides applied after the role-based switch block. The alternative -- a separate ability builder for personal orgs -- would have duplicated the role logic.
Risks
- String subjects limit future expressiveness. If complex conditions become necessary, migrating to typed subjects would require updating all ability definitions. Mitigated by the ABAC-in-router pattern, which does not depend on CASL's condition system.
- AI agents adding allowlist exceptions. An agent could add entries to
AUTHORIZED_PROCEDURE_ALLOWLISTto silence the architecture test. Mitigated by the AI review checklist, which specifically checks for allowlist modifications. - Role explosion at scale. If the company tier grows to 15-20 specialized roles, the single-file RBAC approach may become unwieldy. Mitigated by the planned evaluation of Permit.io or SpiceDB at Phase 3.
Phase 3 Upgrade Path
If Trovella evolves toward real-time collaboration with complex sharing hierarchies, the authorization model would be evaluated for upgrade to:
- Permit.io -- if a non-technical admin needs to manage permissions via UI
- SpiceDB -- if Google Docs-style sharing with relationship hierarchies is needed
- Typed CASL subjects -- if the number of ABAC conditions grows significantly and centralizing them in the ability builder becomes worthwhile