react-auth-gate
v0.0.6
Published
A production-grade React authorization framework for RBAC, PBAC, ABAC, feature flags, and async permission checks
Maintainers
Readme
🔐 react-auth-gate
A production-grade React authorization framework that centralizes RBAC, PBAC, ABAC, feature flags, and async permission checks into a clean, declarative API.
Permission logic never lives inside components again.
📚 Documentation • 🎮 Live Demo
✨ Features
- 🎯 Declarative API - Gate components with simple props
- 🔄 RBAC + PBAC + ABAC - Role, Permission, and Attribute-based access control
- ⚡ Async Support - Check permissions against APIs in real-time
- 🚩 Feature Flags - Built-in feature flag support
- 🎨 Framework Agnostic - Works with any React app (Next.js, CRA, Vite, etc.)
- 📦 Tree-shakeable - Zero runtime overhead for unused features
- 🔍 TypeScript First - Fully typed with excellent IntelliSense
- 🛠️ Dev Tools Panel - Killer feature: Automatic permission debugging panel in development
- 🪶 Lightweight - No heavy dependencies
🚀 Quick Start
Installation
npm install react-auth-gateBasic Usage
import { PermissionsRoot, PermissionsGate } from 'react-auth-gate';
// 1. Define your permission rules
const rules = {
'user.edit': ({ user, resource }) =>
user.role === 'admin' || user.id === resource.id,
'post.delete': ({ user, resource }) =>
user.id === resource.authorId,
};
// 2. Wrap your app
function App() {
return (
<PermissionsRoot
user={currentUser}
roles={['editor']}
permissions={['post.create', 'post.edit']}
rules={rules}
flags={{ newUI: true }}
>
<YourApp />
</PermissionsRoot>
);
}
// 3. Use permission gates anywhere
function UserProfile({ user }) {
return (
<div>
<h1>{user.name}</h1>
<PermissionsGate allow="user.edit" resource={user}>
<EditButton />
</PermissionsGate>
</div>
);
}That's it! Your permissions are centralized, testable, and declarative.
📚 Core Concepts
Permission Rules
Rules are functions that determine access. They receive a context object and return a boolean (or Promise).
type PermissionRule = (ctx: {
user: any; // Current user
resource?: any; // Resource being accessed
roles: string[]; // User's roles
permissions: string[]; // User's permissions
flags: Record<string, boolean>; // Feature flags
}) => boolean | Promise<boolean>;Rule Types
1. Role-Based (RBAC)
const rules = {
'admin.access': ({ roles }) => roles.includes('admin'),
};2. Permission-Based (PBAC)
const rules = {
'post.create': ({ permissions }) => permissions.includes('post.create'),
};3. Attribute-Based (ABAC)
const rules = {
'user.edit': ({ user, resource }) =>
user.role === 'admin' || user.id === resource.id,
};4. Async Rules
const rules = {
'subscription.premium': async ({ user }) => {
const subscription = await checkSubscriptionAPI(user.id);
return subscription.isPremium;
},
};🎯 API Reference
<PermissionsRoot>
The root provider component. Use this to wrap your app with automatic dev tools integration.
<PermissionsRoot
user={currentUser}
roles={['admin', 'editor']}
permissions={['post.edit', 'post.delete']}
rules={rulesMap}
flags={{ newUI: true }}
enableDevTools={true} // default: auto-enabled in development
>
<App />
</PermissionsRoot>Props:
user- Current authenticated userroles?- Array of role stringspermissions?- Array of permission stringsrules?- Map of named permission rulesflags?- Feature flags objectenableDevTools?- Enable/disable dev panel (default: auto in dev mode)
<PermissionsGate>
Declarative permission boundary component.
<PermissionsGate
allow="user.edit"
resource={user}
mode="hide"
fallback={<div>Access Denied</div>}
>
<EditButton />
</PermissionsGate>Props:
allow?- Permission check (string, array, or function)any?- Array of permissions (OR logic)all?- Array of permissions (AND logic)resource?- Resource to check againstmode?-"hide"(default) or"disable"fallback?- React node to show when deniedchildren- Protected content
Examples:
// Single permission
<PermissionsGate allow="admin.access">
<AdminPanel />
</PermissionsGate>
// Multiple permissions (any)
<PermissionsGate any={['admin', 'moderator']}>
<ModPanel />
</PermissionsGate>
// Multiple permissions (all)
<PermissionsGate all={['post.edit', 'post.publish']}>
<PublishButton />
</PermissionsGate>
// Disable mode
<PermissionsGate allow="post.delete" resource={post} mode="disable">
<DeleteButton />
</PermissionsGate>
// Inline rule
<PermissionsGate allow={({ user }) => user.verified}>
<VerifiedBadge />
</PermissionsGate>
// With fallback
<PermissionsGate allow="premium.feature" fallback={<UpgradePrompt />}>
<PremiumContent />
</PermissionsGate>usePermission()
Hook for programmatic permission checks.
const { allowed, loading } = usePermission('user.edit', user);
return (
<button disabled={!allowed || loading}>
{loading ? 'Checking...' : 'Edit'}
</button>
);Returns:
allowed- Boolean indicating if permission is grantedloading- Boolean indicating if check is in progress
Simpler version (no loading state):
const canEdit = usePermissionValue('user.edit', user);<Permissioned>
Render-prop version for maximum control.
<Permissioned allow="post.edit" resource={post}>
{(allowed, loading) => (
<button disabled={!allowed || loading}>
{loading ? 'Checking...' : allowed ? 'Edit' : 'View Only'}
</button>
)}
</Permissioned><ProtectedRoute>
Route protection component (framework-agnostic).
// React Router
<Route
path="/admin"
element={
<ProtectedRoute
allow="admin.access"
fallback={<Navigate to="/login" />}
>
<AdminDashboard />
</ProtectedRoute>
}
/>
// Next.js
function AdminPage() {
const router = useRouter();
return (
<ProtectedRoute
allow="admin"
onAccessDenied={() => router.push('/login')}
>
<AdminPanel />
</ProtectedRoute>
);
}Props:
allow- Permission checkresource?- Resource to checkfallback?- Content when denied (default: unauthorized message)onAccessDenied?- Callback when access deniedchildren- Protected content
🛠️ Dev Tools Panel (The Killer Feature)
In development mode, react-auth-gate automatically renders a floating permission debugger.
Features
✅ Live Permission Tracking - See every permission check as it happens
✅ Pass/Fail Details - Understand why checks succeed or fail
✅ Rule Inspection - See which rules evaluated and their results
✅ Context Override - Test different roles, permissions, and flags
✅ Real-time Simulation - Toggle permissions without code changes
✅ Zero Configuration - Appears automatically when using PermissionsRoot
How to Use
- Use
PermissionsRootinstead ofPermissionsProvider - Run your app in development mode
- Click the 🔐 icon in the bottom-right corner
Panel Tabs
1. Evaluations
- Shows all permission checks in real-time
- Pass/fail status with rule details
- Timestamps and evaluation duration
- Resource information
2. Overrides
- Toggle roles on/off
- Add/remove permissions
- Enable/disable feature flags
- Test different scenarios instantly
3. Context
- View current user object
- See active roles and permissions
- Inspect feature flags
- Debug context values
🎓 Common Patterns
Resource Ownership
const rules = {
'resource.edit': ({ user, resource }) =>
user.role === 'admin' || user.id === resource.ownerId,
};Time-Based Access
const rules = {
'event.register': ({ resource }) => {
const now = Date.now();
return now >= resource.registrationStart && now <= resource.registrationEnd;
},
};Hierarchical Permissions
const rules = {
'content.view': ({ permissions }) =>
permissions.includes('content.view') ||
permissions.includes('content.edit') ||
permissions.includes('content.admin'),
};Complex Business Logic
const rules = {
'order.cancel': ({ user, resource }) => {
// Can't cancel shipped orders
if (resource.status === 'shipped') return false;
// Customer can cancel within 24h
if (user.id === resource.customerId) {
const hoursSinceOrder = (Date.now() - resource.createdAt) / (1000 * 60 * 60);
return hoursSinceOrder < 24;
}
// Admin can always cancel
return user.role === 'admin';
},
};🧪 Testing
Permission rules are pure functions, making them easy to test.
import { rules } from './permissions';
describe('user.edit permission', () => {
it('allows admin to edit any user', () => {
const result = rules['user.edit']({
user: { id: '1', role: 'admin' },
resource: { id: '2' },
roles: ['admin'],
permissions: [],
flags: {},
});
expect(result).toBe(true);
});
it('allows user to edit themselves', () => {
const result = rules['user.edit']({
user: { id: '1', role: 'user' },
resource: { id: '1' },
roles: ['user'],
permissions: [],
flags: {},
});
expect(result).toBe(true);
});
it('denies user from editing others', () => {
const result = rules['user.edit']({
user: { id: '1', role: 'user' },
resource: { id: '2' },
roles: ['user'],
permissions: [],
flags: {},
});
expect(result).toBe(false);
});
});📦 Advanced Usage
Custom Permission Provider
If you need custom integration without dev tools:
import { PermissionsProvider } from 'react-auth-gate';
<PermissionsProvider {...config}>
<App />
</PermissionsProvider>Manual Dev Tools Integration
import { PermissionsProvider, DevPanel, useDevRegister } from 'react-auth-gate';
function Root() {
const registerEvaluation = useDevRegister();
return (
<PermissionsProvider {...config} onEvaluationRegister={registerEvaluation}>
<App />
<DevPanel />
</PermissionsProvider>
);
}Direct Rule Engine Access
import { evaluatePermission, createPermissionContext } from 'react-auth-gate';
const context = createPermissionContext(user, resource, roles, permissions, flags);
const result = await evaluatePermission(check, context, rulesMap);🎨 TypeScript Support
Fully typed with generics for your custom types:
interface User {
id: string;
role: 'admin' | 'user';
}
interface Post {
id: string;
authorId: string;
}
const rules: PermissionRulesMap<User, Post> = {
'post.edit': ({ user, resource }) => {
// Full type safety!
return user.role === 'admin' || user.id === resource?.authorId;
},
};🔧 Framework Integration
React Router
import { ProtectedRoute } from 'react-auth-gate';
import { Navigate } from 'react-router-dom';
<Route
path="/admin"
element={
<ProtectedRoute allow="admin" fallback={<Navigate to="/" />}>
<AdminPage />
</ProtectedRoute>
}
/>Next.js
// pages/admin.tsx
import { ProtectedRoute } from 'react-auth-gate';
import { useRouter } from 'next/router';
export default function AdminPage() {
const router = useRouter();
return (
<ProtectedRoute
allow="admin"
onAccessDenied={() => router.push('/login')}
>
<AdminDashboard />
</ProtectedRoute>
);
}Remix
import { ProtectedRoute } from 'react-auth-gate';
import { useNavigate } from '@remix-run/react';
export default function Route() {
const navigate = useNavigate();
return (
<ProtectedRoute
allow="admin"
onAccessDenied={() => navigate('/login')}
>
<Content />
</ProtectedRoute>
);
}🤔 FAQ
Q: How is this different from checking permissions in components?
A: All permission logic is centralized in the rules map. Components don't contain authorization logic, making them easier to maintain and test.
Q: Can I use this with server-side auth?
A: Yes! This library handles UI-level authorization. Your server should still validate permissions. This prevents unnecessary API calls and provides better UX.
Q: Does this work with Next.js App Router?
A: Yes! Use "use client" for components using the library. Server Components can check permissions differently.
Q: What about bundle size?
A: ~5KB gzipped. Tree-shakeable, so you only pay for what you use.
Q: Can I use this without TypeScript?
A: Yes, but TypeScript is recommended for the best experience.
Q: How do I disable the dev panel in production?
A: It's automatically disabled when process.env.NODE_ENV === 'production'.
🎯 Best Practices
- ✅ Centralize rules - Define all rules in one place
- ✅ Keep rules pure - No side effects in rule functions
- ✅ Test rules independently - Rules are just functions
- ✅ Use TypeScript - Get type safety for users and resources
- ✅ Async sparingly - Async rules add latency
- ✅ Server-side validation - Never trust client-side checks alone
- ✅ Use the dev panel - Debug permissions visually
- ✅ Resource-based when possible - More secure than role-only checks
- ✅ Fallback content - Provide good UX when access is denied
- ✅ Name rules clearly - Use dot notation:
resource.action
📄 License
MIT © 2024
🙌 Contributing
Contributions are welcome! Please open an issue or PR.
