exguard-client
v2.1.9
Published
ExGuard RBAC client with cache-first Redis support for maximum performance in EmpowerX applications
Maintainers
Readme
exguard-client
High-performance RBAC (Role-Based Access Control) client for React.
Uses a Cache-First Redis strategy to ensure permission checks are instant (0ms) and API calls are minimized.
🚀 Quick Start (Recommended)
The fastest way to integrate ExGuard into your React project.
1. Install
pnpm add exguard-client2. Auto-Configure
Run the setup tool to automatically scaffold files and update your code:
npx exguard-setup3. Add Environment Variable
Add your backend URL to your .env file:
VITE_GUARD_API_URL=http://localhost:3000🛠️ What npx exguard-setup does
The setup script automates the following integration steps:
✅ Files Generated
It creates a dedicated feature folder at src/features/exguard/:
index.ts: Central export point for all ExGuard hooks and components.components/system-admin-permission-guard.tsx: A pre-built guard for Elevated/Admin privileges.config/exguard.config.ts: Placeholder for custom app-specific RBAC logic.
✅ Code Modified
main.tsx: InjectssetExGuardConfig,initializeCacheFirstRedisClient(), and wraps your app inExGuardRealtimeProvider→UserAccessProvider. (The script intelligently detects if these are already present to avoid duplicates).protected-route.tsx: Wraps your<Outlet />with the Realtime provider to enable instant RBAC sync.auth-utils.ts: Addswindow.dispatchEvent(new CustomEvent('exguard:token-updated'))to trigger immediate permission refreshes on login.
Expected main.tsx after setup:
import { UserAccessProvider, ExGuardRealtimeProvider, setExGuardConfig, initializeCacheFirstRedisClient } from 'exguard-client';
// Auto-injected Configuration
setExGuardConfig({
apiUrl: import.meta.env.VITE_GUARD_API_URL,
withCredentials: true,
});
initializeCacheFirstRedisClient();
createRoot(document.getElementById('root')!).render(
<ExGuardRealtimeProvider>
<UserAccessProvider>
<App />
</UserAccessProvider>
</ExGuardRealtimeProvider>
);📖 Usage Examples
1. Conditional Navigation (Sidebar/Menu)
Hide or show menu items based on user permissions.
import { useUserAccessCacheFirst } from '@/features/exguard';
import { Link } from 'react-router-dom';
function Sidebar() {
const { hasPermission, hasModulePermission } = useUserAccessCacheFirst();
return (
<nav>
<Link to="/dashboard">Dashboard</Link>
{/* Show based on specific permission */}
{hasPermission('users:view') && (
<Link to="/users">Manage Users</Link>
)}
{/* Show entire section only if user has access to the module */}
{hasModulePermission('inventory') && (
<div className="section">
<h3>Inventory</h3>
<Link to="/stock">Stock Levels</Link>
{hasPermission('inventory:admin') && <Link to="/settings">Inventory Settings</Link>}
</div>
)}
</nav>
);
}2. Displaying User Details from Cached Data
Extract and display the authenticated user's information.
import { useUserAccessCacheFirst } from '@/features/exguard';
function UserProfile() {
const { userAccess, isLoading } = useUserAccessCacheFirst();
if (isLoading) return <div>Loading user...</div>;
if (!userAccess) return null;
const fullName = `${userAccess.user.givenName} ${userAccess.user.familyName}`;
const fieldOffice = userAccess.user.fieldOffice?.name ?? 'N/A';
return (
<div className="user-profile">
<p><strong>Name:</strong> {fullName}</p>
<p><strong>Field Office:</strong> {fieldOffice}</p>
</div>
);
}Or using useUserAccessSingleton:
import { useUserAccessSingleton } from '@/features/exguard';
function SidebarHeader() {
const { userAccess } = useUserAccessSingleton();
if (!userAccess) return null;
return (
<div className="sidebar-header">
<span className="user-name">
{userAccess.user.givenName} {userAccess.user.familyName}
</span>
<span className="user-office">
{userAccess.user.fieldOffice?.name ?? 'No Field Office'}
</span>
</div>
);
}3. Button & Action Protection
Disable or hide buttons for unauthorized actions.
function UserRow({ user }) {
const { hasPermission } = useUserAccessCacheFirst();
return (
<tr>
<td>{user.name}</td>
<td>
<button disabled={!hasPermission('users:edit')}>
Edit
</button>
{hasPermission('users:delete') && (
<button className="delete-btn">Delete</button>
)}
</td>
</tr>
);
}4. Protecting Entire Pages (Routes)
Use the PermissionGuard to wrap routes in your main router configuration.
import { PermissionGuard } from '@/features/exguard';
// In your routes file:
const router = createBrowserRouter([
{
path: "/admin",
element: (
<PermissionGuard module="system" permission="admin:access">
<AdminLayout />
</PermissionGuard>
),
children: [
{ path: "settings", element: <SettingsPage /> }
]
}
]);⚡ How Real-time Updates Work
ExGuard ensures your frontend permissions stay in sync with the backend without requiring a page refresh.
- Backend Change: An admin updates a user's role in the dashboard.
- WebSocket Signal: The backend sends a targeted "RBAC Update" signal to the user's browser.
- Instant Invalidation: The
ExGuardRealtimeProviderreceives the signal and instantly invalidates the local cache. - Auto-Refresh: All active
useUserAccessCacheFirsthooks detect the change and re-sync with the latest permissions from Redis (0.5ms - 2ms). - UI Update: Your sidebar, buttons, and guards update immediately to reflect the new permissions.
💡 Key Concepts
- Redis-First: The client always tries to fetch permissions from Redis (via your backend proxy) before falling back to an API call.
- Real-time: When a user's role changes on the backend, the frontend cache is invalidated instantly via WebSockets.
- Zero Latency: Once the cache is loaded,
hasPermission()calls do not trigger network requests.
❓ Troubleshooting
| Issue | Solution |
| :--- | :--- |
| Permissions returning false | Ensure your .env URL is correct and the user has a valid token. |
| Changes not reflecting | Call invalidateCache() from the hook or refresh the page. |
| 401 Unauthorized | Check if access_token is present in LocalStorage. |
Built for speed. Designed for scale.
