When we started building our healthcare platform, we thought standard role-based access control would be enough. After all, most SaaS applications work fine with simple roles like "admin," "user," or "moderator." But healthcare proved us wrong — fast.
Within weeks, we discovered that basic RBAC couldn't handle the intricate web of permissions that real medical operations demand. A single user might need different access levels across multiple clinics, temporary permissions for specific patients, and instant revocation capabilities for compliance. Traditional RBAC systems simply weren't designed for this level of complexity.
What we needed wasn't just user roles — we needed contextual access control that considered who was accessing what, when, where, and under what circumstances. Here's how we built it.
Basic role-based access control (RBAC) might work fine for consumer apps, but healthcare demands something much more sophisticated. Here's why:
We weren't just building RBAC. We were creating Contextual Access Control — a system that considers role + tenant + data + time + device all at once.
First, we used acts_as_tenant to make sure all data stayed within organization boundaries.
We set the current organization early in every request:
This ensured all database queries respected tenant boundaries automatically — even in background jobs.
To prevent data leaks even if our code had bugs, we added PostgreSQL Row-Level Security:
We pushed user and organization context directly into the database connection. The current_user_has_permission function used a fast materialized view for permission checks.
This locked down access at the database level — protecting us even if Rails made mistakes.
Instead of fixed roles, we built a dynamic system:
Membership
- user_id
- organization_id
- role_id
Role
- name
has_many :permissions
Permission
- action (e.g. "read")
- subject_class (e.g. "Appointment")
- conditions (JSON field for dynamic rules)
This supported:
We extended Pundit to handle our complex permission logic:
This enabled record-level access control with flexible policy rules.
We sent permission data to the frontend as compiled JSON:
A simple helper function handled access checks:
We considered using CASL or Permissive, but built our own system to ensure perfect alignment between frontend and backend policies.
We built comprehensive testing and monitoring tools:
Our system now handles:
Q: Why use Postgres RLS when you already have acts_as_tenant?
acts_as_tenant protects at the application level, but RLS protects at the database level. RLS prevents data leaks even if developers accidentally write unsafe queries or admin tools.
Q: Can users work with multiple organizations?
Yes. Our membership model supports users having different roles across multiple organizations.
Q: How do you handle per-record permissions?
We store conditional logic in JSON (like "only allow edit if assigned_doctor_id equals current_user.id"). These conditions work on both backend and frontend.
Q: What happens when permissions change after login?
Permissions update dynamically. We can push changes through real-time channels or refresh them during key actions.
Q: Why not use existing permission libraries?
We needed perfect alignment between frontend and backend. Our custom system uses the same permission schema everywhere, eliminating inconsistencies.
This wasn't just a permission system — it was the foundation that enabled safe expansion across a national healthcare network. When dealing with multi-tenant applications requiring fine-grained access control, you don't need another gem. You need a comprehensive strategy.
Ready to build robust access control for your complex application? Contact TechDots to discuss your specific requirements.
Techdots has helped 15+ founders transform their visions into market-ready AI products. Each started exactly where you are now - with an idea and the courage to act on it.
Techdots: Where Founder Vision Meets AI Reality
Book Meeting