architecture rbac auth design

RBAC: The Bouncer Your API Deserves

4 min read

🚧 RBAC: The Bouncer Your API Deserves (ELI5 + Fun Edition πŸŽ‰)

Think of your API like a hospital with many rooms πŸ₯πŸšͺ

  • Everyone with a badge can enter the building (logged in βœ…)
  • But not everyone should enter every room (permissions ❌/βœ…)

Right now, we only check "is this a real badge?" πŸͺͺ We do not check "can this person open this room?" πŸ”

That’s the RBAC mission: right person, right room, right action 🧠✨


⚑ TL;DR (One-Screen Version)

Before: User has role_id=2 and can do almost anything 😬

After: User has a role + allowed actions like:

  • order.delete πŸ—‘οΈ
  • patient.patdiag.update ✏️
  • result.view πŸ‘€

Every protected endpoint checks that list. No permission = 403 Forbidden 🚫


😡 The Current Situation (Why it hurts)

Current flow:

Client -> JWT -> AuthFilter -> Controller

What we check now:

  • token exists βœ…
  • token not expired βœ…

What we don’t check:

  • what role means 🀷
  • what user can do 🀷

So controllers are mostly "open by default" (yikes) 🫠


🎯 What We Need

From your real examples:

  • Only Role A can delete OrderTest πŸ—‘οΈ
  • Only Role B can edit PatDiag ✍️
  • Most other normal actions stay available to authenticated users πŸ‘

So we need action-level permissions (not just broad role names).


🧱 Simple RBAC Model

We add 3 core tables:

  1. roles πŸ‘₯
    Human names like Admin, Doctor, Technician.

  2. permissions 🧾
    Small actions like order.delete, patient.view.

  3. role_permissions πŸ”—
    The mapping table: which role gets which permission.

And we make sure users.role_id actually points to roles.id.


🏷️ Permission Naming (boring on purpose = good)

Use:

resource.action

Examples:

  • order.delete
  • patient.patdiag.update
  • result.approve
  • admin.manage

Why this format?

  • easy to read πŸ“–
  • easy to search πŸ”
  • easy to debug πŸ› οΈ

πŸ› οΈ Build Plan (Dumb + Practical + No Drama)

Phase 1: Create RBAC tables πŸ—οΈ

  • create roles
  • create permissions
  • create role_permissions
  • add FK from users.role_id to roles.id
  • seed starter roles

Phase 2: Put permissions in JWT at login πŸš€

On login:

  1. load user role
  2. load role permissions
  3. add permission strings to JWT payload

Then request checks are fast (no DB hit every request) ⚑

Phase 3: Add a PermissionFilter πŸšͺ

Reusable filter behavior:

  • read required permission from route
  • admin bypass (if we keep that rule)
  • check if permission exists in token
  • if missing -> return 403

Phase 4: Protect routes/controllers πŸ›‘οΈ

  • simple endpoint? use route filter directly
  • mixed endpoint (does many things)? check permission inside controller before sensitive section

Phase 5: Add admin management endpoints πŸŽ›οΈ

Admin can:

  • list permissions
  • create roles
  • assign permissions to roles

Gate this behind admin.manage πŸ‘‘


🌱 Starter Permission Set

  • order.delete -> Role A only
  • patient.patdiag.update -> Role B only
  • patient.view -> all authenticated
  • result.view -> all authenticated
  • admin.manage -> admin only

Grow from here, one permission at a time 🌿


⚠️ Risks (Realistic)

  1. Token gets bigger πŸ”
    Usually fine unless you have huge permission lists.

  2. Audit takes time ⏳
    Many controllers may need checks; do incremental rollout.

  3. Mixed endpoints are tricky 🧩
    Need clear rule: fail whole request vs fail only restricted part.


❓ Open Questions

  1. What are your actual role names today?
  2. Do admins bypass everything, or still need explicit permissions?
  3. For mixed updates, do we fail all or partially apply?
  4. Keep resource.action naming, or add module prefix (lab.order.delete)?
  5. Rollout style: full audit now or start with OrderTest + PatDiag first?
  6. If role/permission changes, how do we invalidate old JWTs?

πŸ“¦ File Manifest (implementation touchpoints)

  • app/Database/Migrations/...CreateRbacTables.php (new)
  • app/Database/Seeds/RbacSeeder.php (new)
  • app/Filters/PermissionFilter.php (new)
  • app/Traits/PermissionTrait.php (new)
  • app/Config/Filters.php (edit)
  • app/Controllers/AuthController.php (edit)
  • app/Config/Routes/08-order.php (edit)
  • app/Config/Routes/01-patient.php (edit)
  • app/Controllers/PatVisit/PatVisitController.php (edit)
  • app/Controllers/Admin/PermissionController.php (new)
  • app/Config/Routes/14-admin.php (new)

πŸŽ‰ Final ELI5

RBAC is just this:

"Being logged in means you entered the building.
Permissions decide which doors you can open." πŸ”

That’s it. Keep doors explicit, checks consistent, and 403s honest. πŸ˜„πŸ”₯