Enterprise-grade authentication, authorization, and feature flag management system built with React.
ControlHub demonstrates senior-level React architecture patterns including:
- Role-based access control (RBAC)
- Feature flags with role-scoped visibility
- Granular error boundaries
- Lazy loading by module
- Clean separation of concerns
| Technology | Purpose |
|---|---|
| React 18 | UI Framework |
| TypeScript | Type safety |
| React Router v6 | Routing & navigation |
| Zustand | State management |
| MSW | Mock API (Service Worker) |
| Vite | Build tool |
# Install dependencies
npm install
# Start development server
npm run devOpen http://localhost:5173 and login with test credentials.
| Role | Password | |
|---|---|---|
| Admin | admin@test.com | admin123 |
| Editor | editor@test.com | editor123 |
| Viewer | viewer@test.com | viewer123 |
src/
├── app/ # App entry point & providers
├── auth/ # Authentication module
│ ├── store/ # Zustand store
│ ├── hooks/ # useAuth hook
│ ├── types/ # TypeScript types
│ └── utils/ # Token utilities
├── permissions/ # Authorization module
│ ├── store/ # Permissions store
│ ├── hooks/ # usePermissions, useHasPermission
│ └── components/ # PermissionGate
├── features/ # Feature flags module
│ ├── store/ # Feature flags store
│ ├── hooks/ # useFeatureFlag
│ ├── components/ # FeatureGate
│ └── config/ # Feature definitions
├── routes/ # Routing configuration
│ └── guards/ # AuthGuard, RoleGuard, FeatureGuard
├── layouts/ # Layout components by role
├── errors/ # Error boundaries & fallbacks
├── components/ # Shared UI components
├── pages/ # Page components
└── mocks/ # MSW handlers & mock data
┌─────────────────────────────────────────────────────────────┐
│ ADMIN │
│ users:* | content:* | reports:* | settings:* | features:* │
├─────────────────────────────────────────────────────────────┤
│ EDITOR │
│ content:read,write,delete | reports:read | settings:read │
├─────────────────────────────────────────────────────────────┤
│ VIEWER │
│ content:read | reports:read │
├─────────────────────────────────────────────────────────────┤
│ GUEST │
│ (no permissions - public routes only) │
└─────────────────────────────────────────────────────────────┘
| Feature | Roles | Default |
|---|---|---|
| new-dashboard | admin, editor | enabled |
| export-data | admin | enabled |
| beta-mode | admin | disabled |
| advanced-reports | admin, editor, viewer | enabled |
| bulk-actions | admin, editor | enabled |
// auth.store.ts - Single source of truth
const useAuthStore = create<AuthState>((set) => ({
user: null,
token: null,
isAuthenticated: false,
login: async (credentials) => { /* ... */ },
logout: () => { /* ... */ },
}));Why: Components should only consume state, never manage auth logic directly. This ensures consistency across the app and makes testing easier.
<AuthGuard>
<RoleGuard allowedRoles={['admin', 'editor']}>
<FeatureGuard feature="new-dashboard">
<Dashboard />
</FeatureGuard>
</RoleGuard>
</AuthGuard>Why: Single responsibility principle. Each guard does one thing. They can be composed as needed without tight coupling.
const feature = {
id: 'export-data',
enabled: true,
roles: ['admin'], // Only visible to admins even when enabled
};Why: Real-world feature flags often need role-based visibility. A feature might be "on" but only for certain user segments.
const AdminDashboard = lazy(() => import('./pages/admin/Dashboard'));
const EditorContent = lazy(() => import('./pages/editor/Content'));Why: Users only download code for their role. Admins don't load editor code, editors don't load admin code. Smaller bundles, faster loads.
App
└── GlobalErrorBoundary (catches everything)
└── Layout
└── ModuleErrorBoundary (catches module errors)
└── Page (if this crashes, only this section shows error)
Why: One component crashing shouldn't take down the entire app. Users see a scoped error message and can still navigate.
- Permissions: What a user CAN do (CRUD operations)
- Feature Flags: What the SYSTEM allows (toggle functionality)
// Permission: Can user export?
<PermissionGate permission="reports:export">
{/* Feature: Is export enabled? */}
<FeatureGate feature="export-data">
<ExportButton />
</FeatureGate>
</PermissionGate>1. App loads → checkSession()
├── Token exists & valid → Hydrate user state
└── No/invalid token → Show login
2. User logs in → POST /api/auth/login
├── Success → Store token, set user, redirect to dashboard
└── Failure → Show error, stay on login
3. Protected route access → AuthGuard
├── Authenticated → RoleGuard → FeatureGuard → Render
└── Not authenticated → Redirect to /login (preserve intended URL)
4. Logout → Clear token & state → Redirect to home
- Mock API only - No real backend. Data resets on page refresh.
- No token refresh - JWT expiry is simple (24h), no refresh token flow.
- Client-side security - Guards are for UX, not security. Real API should validate too.
- No persistent feature flags - Toggles reset on refresh.
- Basic error logging - Only console.error, no monitoring service.
- Token refresh mechanism - Automatic token renewal before expiry
- Server-side session validation - Don't trust client state alone
- Audit logging - Track who changed what and when
- Feature flag persistence - Store flag state in database
- A/B testing support - Random flag assignment for experiments
- Rate limiting - Prevent brute force attacks
- CSRF protection - For state-changing requests
- Real error tracking - Sentry, DataDog, etc.
npm run dev # Start dev server
npm run build # Production build
npm run preview # Preview production build
npm run lint # Run ESLintMIT