Skip to main content
Enterprise SaaS
Fractional Frontend Architect
8 weeks
2025

Building an Enterprise-Grade RBAC Team Management System

The platform had no teams, no roles, and no audit trail. Enterprise customers were sharing credentials.

Client name withheld under NDA. Industry, scope, and results are accurate.

Share:
5
Granular permission roles
100%
Audit coverage on mutations
0
Authorization bypass incidents post-launch

The Challenge

The product had launched as a single-user platform — one account, one set of permissions, no concept of teams. It worked fine for early adopters, but now mid-market companies were in the sales pipeline asking for multi-seat access, and every deal was stalling at the same question: "How do we manage permissions?"

When I audited the codebase, the authorization picture was worse than expected. Some endpoints checked ownership, others didn't — it was scattered and inconsistent. There was no invitation system, which meant new team members were literally sharing login credentials — a non-starter for any company going through SOC 2. And zero audit logging. The platform couldn't answer the most basic governance question: who changed what, and when?

The founders needed this fixed urgently, but couldn't afford to derail the feature roadmap to do it.

The Approach

I started by mapping every API endpoint and UI action against a permission matrix — which turned out to be the most valuable artifact of the whole engagement. Working with the product team, we settled on five roles: owner, admin, editor, viewer, and billing.

I deliberately chose flat permission tokens (e.g., projects:write, team:manage, billing:read) over a hierarchical model. Hierarchies look elegant in diagrams but become a debugging nightmare when you need to answer "why can't this user do X?" Flat tokens are boring, predictable, and easy to test. That's the point.

On the backend (Node.js / Express / MongoDB), I replaced the scattered ownership checks with a single authorize middleware. One code path, one place to audit, one place where authorization logic lives. Every mutation endpoint got wrapped with an audit-log writer that captured the actor, action, target, timestamp, and a before/after diff of changed fields.

The invitation system was where the interesting edge cases lived. Email-based invites with cryptographically signed, time-limited tokens — straightforward. But the real work was handling what happens when:

  • You invite someone who already has an account (merge into workspace, don't create a duplicate).
  • You invite someone who doesn't have an account yet (deferred activation).
  • An invitation gets revoked before it's accepted.
  • An editor tries to invite someone as admin (role-escalation prevention).

These aren't exotic scenarios — they're the first things a QA engineer or a malicious user will try.

On the React frontend, I built a <PermissionGate> component and a usePermission hook. The permission set gets hydrated at login into a lightweight Zustand store, and every button, menu item, and route segment is declaratively gated. Users only see actions they're authorized to perform — eliminating an entire category of "I clicked this and got an error" support tickets.

Tech Stack

React 18
TypeScript
Node.js
Express
MongoDB
Zustand
Zod
Resend
Vitest

The Results

The system launched to ten enterprise pilot customers with zero authorization bypass incidents in the first three months.

What mattered most:

  • Five clearly scoped roles replaced all-or-nothing access. Every enterprise sales call had been asking for this — now it was a checkbox.
  • Full audit logging on every mutation. The company passed its initial SOC 2 Type I readiness review with no remediation items related to access control. That alone probably saved them months.
  • Onboarding time: ~20 min → under 2 min. Clicking a link beats sharing credentials in a Slack DM.
  • The <PermissionGate> pattern got adopted as the team standard for all new features — the kind of outcome that tells you the architecture was right.

The system was designed to be extensible: adding a new role or permission requires a schema update and a migration script, not structural code changes.

More Case Studies

Get started

Ready to Level Up Your Product?

I take on a handful of companies at a time. Reach out to discuss your challenges and see if there's a fit.

Send a Message