React Integration
Fire Shield provides hooks and components for React applications.
Installation
bash
npm install @fire-shield/react@3.1.1 @fire-shield/core@3.1.1Setup
Basic Setup
typescript
// App.tsx
import { RBACProvider } from '@fire-shield/react'
import { RBAC } from '@fire-shield/core'
import { useState } from 'react'
// Initialize RBAC
const rbac = new RBAC()
rbac.createRole('admin', ['posts:*', 'users:*'])
rbac.createRole('editor', ['posts:read', 'posts:write'])
rbac.createRole('viewer', ['posts:read'])
function App() {
const [user, setUser] = useState({
id: '1',
roles: ['editor']
})
return (
<RBACProvider rbac={rbac} user={user}>
<YourApp />
</RBACProvider>
)
}Hooks
Hooks
Access RBAC functionality in your components using the dedicated hooks:
tsx
import { useRBAC, usePermission, useRole, useUser } from '@fire-shield/react'
function PostEditor() {
const rbac = useRBAC() // Returns RBAC instance directly
const user = useUser() // Returns RBACUser | null
const canWrite = usePermission('posts:write')
const canDelete = usePermission('posts:delete')
const isAdmin = useRole('admin')
const handleDelete = () => {
if (!canDelete) {
alert('No permission to delete')
return
}
deletePost()
}
return (
<div>
<h2>Post Editor</h2>
<p>Current user: {user?.id}</p>
{canWrite && (
<button onClick={createPost}>Create Post</button>
)}
{canDelete && (
<button onClick={handleDelete}>Delete Post</button>
)}
{isAdmin && (
<button onClick={openAdminPanel}>Admin Panel</button>
)}
</div>
)
}Hook API
typescript
// Returns RBAC instance from context
useRBAC(): RBAC
// Check if current user has permission - returns boolean
usePermission(permission: string): boolean
// Check if current user has role - returns boolean
useRole(role: string): boolean
// Get current user from context
useUser(): RBACUser | null
// Get full authorization result
useAuthorize(permission: string): AuthorizationResult
// Check if user has ALL specified permissions
useAllPermissions(...permissions: string[]): boolean
// Check if user has ANY of the specified permissions
useAnyPermission(...permissions: string[]): boolean
// Returns a function to explicitly deny a permission for the current user
useDenyPermission(): (permission: string) => void
// Returns a function to remove a previously denied permission
useAllowPermission(): (permission: string) => void
// Returns array of explicitly denied permissions for the current user
useDeniedPermissions(): string[]
// Check if a specific permission is explicitly denied for the current user
useIsDenied(permission: string): booleanComponents
Can Component
Conditionally render content based on permissions:
tsx
import { Can } from '@fire-shield/react'
function PostActions() {
return (
<div>
<Can permission="posts:write">
<button>Create Post</button>
</Can>
<Can permission="posts:delete" fallback={<p>No permission</p>}>
<button>Delete Post</button>
</Can>
</div>
)
}Cannot Component
Inverse of Can component:
tsx
import { Cannot } from '@fire-shield/react'
function UpgradePrompt() {
return (
<Cannot permission="premium:access">
<div className="upgrade-banner">
<p>Upgrade to unlock premium features</p>
<button>Upgrade Now</button>
</div>
</Cannot>
)
}RequirePermission Component
Throw error or show fallback if permission is missing:
tsx
import { RequirePermission } from '@fire-shield/react'
function AdminPanel() {
return (
<RequirePermission
permission="admin:access"
fallback={<div>Access Denied</div>}
>
<div>
<h1>Admin Panel</h1>
{/* Admin content */}
</div>
</RequirePermission>
)
}Denied Component
Render if the user has the permission explicitly denied:
tsx
import { Denied, NotDenied } from '@fire-shield/react'
function PostActions() {
return (
<div>
{/* Shows children only when permission is explicitly denied */}
<Denied permission="posts:delete">
<p>Your delete access has been revoked.</p>
</Denied>
{/* Shows children when permission is NOT explicitly denied */}
<NotDenied permission="posts:delete">
<button>Delete Post</button>
</NotDenied>
</div>
)
}Deny / Allow Permissions at Runtime
tsx
import { useDenyPermission, useAllowPermission, useDeniedPermissions } from '@fire-shield/react'
function AdminControls() {
const denyPermission = useDenyPermission()
const allowPermission = useAllowPermission()
const denied = useDeniedPermissions()
return (
<div>
<p>Denied: {denied.join(', ')}</p>
<button onClick={() => denyPermission('posts:delete')}>Revoke Delete</button>
<button onClick={() => allowPermission('posts:delete')}>Restore Delete</button>
</div>
)
}React Router Integration
Protected Routes
tsx
import { Navigate } from 'react-router-dom'
import { usePermission } from '@fire-shield/react'
function ProtectedRoute({ permission, children }) {
const hasPermission = usePermission(permission)
if (!hasPermission) {
return <Navigate to="/unauthorized" replace />
}
return children
}
// Usage in router
<Routes>
<Route
path="/admin"
element={
<ProtectedRoute permission="admin:access">
<AdminPage />
</ProtectedRoute>
}
/>
</Routes>Route Guards
Note: React hooks cannot be called inside route loaders. Use the built-in ProtectedRoute component from @fire-shield/react for route-level protection:
tsx
import { ProtectedRoute } from '@fire-shield/react'
// Usage with react-router-dom
<Route
path="/admin"
element={
<ProtectedRoute permission="admin:access" redirectTo="/unauthorized">
<AdminLayout />
</ProtectedRoute>
}
/>Updating User
Update user permissions dynamically by passing a new user prop to RBACProvider:
tsx
import { useState } from 'react'
import { RBACProvider } from '@fire-shield/react'
import { useUser } from '@fire-shield/react'
// In a parent component, manage user state and pass it to RBACProvider
function App() {
const [user, setUser] = useState({ id: '1', roles: ['editor'] })
const switchToAdmin = () => {
setUser({ id: 'admin-1', roles: ['admin'] })
}
const switchToViewer = () => {
setUser({ id: 'viewer-1', roles: ['viewer'] })
}
return (
<RBACProvider rbac={rbac} user={user}>
<div>
<p>Current user: {user?.id}</p>
<button onClick={switchToAdmin}>Switch to Admin</button>
<button onClick={switchToViewer}>Switch to Viewer</button>
</div>
</RBACProvider>
)
}TypeScript Support
Full TypeScript support with type inference:
typescript
import { usePermission, useRole, useUser } from '@fire-shield/react'
import type { RBACUser } from '@fire-shield/core'
// Type-safe user
const user: RBACUser = {
id: 'user-123',
roles: ['editor']
}
// Type-safe hooks
const canWrite = usePermission('posts:write') // boolean
const isEditor = useRole('editor') // booleanBest Practices
1. Use Components for JSX
tsx
// ✅ Good: Declarative and clean
<Can permission="posts:delete">
<button>Delete</button>
</Can>
// ❌ Avoid: Less readable
{canDelete && <button>Delete</button>}2. Use Hooks for Logic
tsx
// ✅ Good: Clear permission check
import { usePermission } from '@fire-shield/react'
const canDelete = usePermission('posts:delete')
const handleDelete = () => {
if (!canDelete) {
showError('No permission')
return
}
deletePost()
}3. Memoize Permission Checks
tsx
import { usePermission } from '@fire-shield/react'
function ExpensiveComponent() {
const canWrite = usePermission('posts:write')
const canDelete = usePermission('posts:delete')
const canPublish = usePermission('posts:publish')
return (
// Use permission values
)
}4. Handle Loading States
tsx
import { useUser, usePermission } from '@fire-shield/react'
function UserProfile() {
const user = useUser()
const canEdit = usePermission('profile:edit')
if (!user) {
return <div>Loading...</div>
}
return (
<div>
<h1>{user.id}</h1>
{canEdit && <EditButton />}
</div>
)
}Server-Side Rendering (SSR)
Next.js Integration
For Next.js, use the dedicated @fire-shield/next package which provides server-side support.
Other SSR Frameworks
Provide initial user state from server:
tsx
// server.tsx
const initialUser = await getUserFromSession(req)
const html = renderToString(
<RBACProvider rbac={rbac} user={initialUser}>
<App />
</RBACProvider>
)Testing
Testing Components with RBAC
tsx
import { render, screen } from '@testing-library/react'
import { RBACProvider } from '@fire-shield/react'
import { RBAC } from '@fire-shield/core'
function renderWithRBAC(component, user) {
const rbac = new RBAC()
rbac.createRole('admin', ['posts:*'])
rbac.createRole('viewer', ['posts:read'])
return render(
<RBACProvider rbac={rbac} user={user}>
{component}
</RBACProvider>
)
}
test('shows delete button for admin', () => {
renderWithRBAC(
<PostActions />,
{ id: '1', roles: ['admin'] }
)
expect(screen.getByText('Delete Post')).toBeInTheDocument()
})
test('hides delete button for viewer', () => {
renderWithRBAC(
<PostActions />,
{ id: '2', roles: ['viewer'] }
)
expect(screen.queryByText('Delete Post')).not.toBeInTheDocument()
})Examples
Blog Post Management
tsx
import { Can, usePermission } from '@fire-shield/react'
function PostCard({ post }) {
const canPublish = usePermission('posts:publish')
return (
<div className="post-card">
<h3>{post.title}</h3>
<p>{post.excerpt}</p>
<div className="actions">
<Can permission="posts:read">
<button>Read More</button>
</Can>
<Can permission="posts:write">
<button>Edit</button>
</Can>
<Can permission="posts:delete">
<button>Delete</button>
</Can>
{canPublish && !post.published && (
<button>Publish</button>
)}
</div>
</div>
)
}User Management Dashboard
tsx
import { RequirePermission, Can } from '@fire-shield/react'
function UserManagement() {
return (
<RequirePermission permission="users:read">
<div>
<h1>User Management</h1>
<Can permission="users:create">
<button>Add New User</button>
</Can>
<UserList />
<Can permission="users:export">
<button>Export Users</button>
</Can>
</div>
</RequirePermission>
)
}Next Steps
- Explore API Reference
- Learn about Permissions
- Check out Examples
