Usage Guide
A comprehensive guide to using @iamjs/react for role-based access control in React applications.
Introduction
@iamjs/react
is a powerful library for implementing role-based access control (RBAC) in React applications. It provides a set of tools and components to easily manage and check permissions based on user roles, allowing for fine-grained control over what users can see and do in your application.
Basic Concepts
- Role: Defines a set of permissions for various resources and actions.
- Schema: A collection of roles used for authorization.
- Resources: The objects or entities that are being protected (e.g., 'posts', 'users').
- Actions: The operations that can be performed on resources (e.g., 'read', 'write', 'delete').
Setting Up Roles and Schema
Let's create a more comprehensive example with multiple roles for a blog application:
import { Role } from '@iamjs/core';
import { createSchema } from '@iamjs/react';
const readerRole = new Role({
name: 'reader',
config: {
posts: {
base: 'r--l',
custom: {
like: true,
comment: true
}
},
profile: {
base: 'r---',
custom: {
viewPublicInfo: true
}
}
}
});
const authorRole = new Role({
name: 'author',
config: {
posts: {
base: 'crudl',
custom: {
publish: true,
unpublish: true
}
},
profile: {
base: 'rud-',
custom: {
changePassword: true,
updateBio: true
}
},
comments: {
base: 'crudl',
custom: {
moderate: true
}
}
}
});
const adminRole = new Role({
name: 'admin',
config: {
posts: {
base: 'crudl',
custom: {
publish: true,
unpublish: true,
feature: true
}
},
users: {
base: 'crudl',
custom: {
ban: true,
unban: true,
changeRole: true
}
},
comments: {
base: 'crudl',
custom: {
moderate: true,
delete: true
}
},
system: {
base: 'crudl',
custom: {
configureSite: true,
viewAnalytics: true
}
}
}
});
const schema = createSchema({ reader: readerRole, author: authorRole, admin: adminRole });
Using the useAuthorization Hook
Here's a more detailed example of using the useAuthorization
hook in a component:
import React from 'react';
import { useAuthorization } from '@iamjs/react';
function BlogPost({ post, currentUser }) {
const auth = useAuthorization(schema);
const canEdit = auth.can(currentUser.role, 'posts', 'update');
const canDelete = auth.can(currentUser.role, 'posts', 'delete');
const canModerateComments = auth.can(currentUser.role, 'comments', 'moderate');
return (
<div className="blog-post">
<h1>{post.title}</h1>
<p>{post.content}</p>
{canEdit && <button onClick={() => editPost(post.id)}>Edit Post</button>}
{canDelete && <button onClick={() => deletePost(post.id)}>Delete Post</button>}
<CommentsSection postId={post.id} />
{canModerateComments && <ModerateCommentsPanel postId={post.id} />}
</div>
);
}
Authorization Methods
can
Here's a more complex example using can
with multiple actions:
function UserProfileActions({ userId, currentUser }) {
const auth = useAuthorization(schema);
const canManageUser = auth.can(
currentUser.role,
'users',
['update', 'delete', 'ban'],
true // strict mode
);
return (
<div className="user-actions">
{canManageUser ? (
<>
<button onClick={() => updateUser(userId)}>Update User</button>
<button onClick={() => deleteUser(userId)}>Delete User</button>
<button onClick={() => banUser(userId)}>Ban User</button>
</>
) : (
<p>You don't have permission to manage this user.</p>
)}
</div>
);
}
authorize
Using authorize
for more granular control:
function AdminDashboard({ currentUser }) {
const auth = useAuthorization(schema);
const canAccessAnalytics = auth.authorize({
role: currentUser.role,
resources: 'system',
actions: 'viewAnalytics'
});
const canManageSite = auth.authorize({
role: currentUser.role,
resources: 'system',
actions: ['configureSite', 'viewAnalytics'],
strict: true
});
return (
<div className="admin-dashboard">
<h1>Admin Dashboard</h1>
{canAccessAnalytics && <AnalyticsPanel />}
{canManageSite && <SiteConfigurationPanel />}
</div>
);
}
use
Utilizing the use
method for role-specific logic:
function UserRoleInfo({ userId }) {
const auth = useAuthorization(schema);
const [userRole, setUserRole] = React.useState(null);
React.useEffect(() => {
// Simulating an API call to get user's role
fetchUserRole(userId).then(role => setUserRole(role));
}, [userId]);
if (!userRole) return <p>Loading...</p>;
const roleInstance = auth.use(userRole);
return (
<div className="user-role-info">
<h2>User Permissions</h2>
<ul>
<li>Can create posts: {roleInstance.can('posts', 'create') ? 'Yes' : 'No'}</li>
<li>Can moderate comments: {roleInstance.can('comments', 'moderate') ? 'Yes' : 'No'}</li>
<li>Can access analytics: {roleInstance.can('system', 'viewAnalytics') ? 'Yes' : 'No'}</li>
</ul>
</div>
);
}
Conditional Rendering with Show Component
A more complex example using nested Show
components:
function BlogPostManager({ post, currentUser }) {
const auth = useAuthorization(schema);
return (
<div className="blog-post-manager">
<h1>{post.title}</h1>
<auth.Show role={currentUser.role} resources="posts" actions="read">
<p>{post.content}</p>
<auth.Show role={currentUser.role} resources="posts" actions="update">
<button onClick={() => editPost(post.id)}>Edit Post</button>
</auth.Show>
<auth.Show role={currentUser.role} resources="posts" actions="delete">
<button onClick={() => deletePost(post.id)}>Delete Post</button>
</auth.Show>
<auth.Show role={currentUser.role} resources="comments" actions={['read', 'create']}>
<CommentsSection postId={post.id} />
<auth.Show role={currentUser.role} resources="comments" actions="moderate">
<ModerateCommentsPanel postId={post.id} />
</auth.Show>
</auth.Show>
</auth.Show>
</div>
);
}
Dynamic Role Building
An example of using dynamic role building in a user management component:
function UserPermissionManager({ userId }) {
const auth = useAuthorization(schema);
const [userPermissions, setUserPermissions] = React.useState(null);
React.useEffect(() => {
// Simulating an API call to get user's custom permissions
fetchUserPermissions(userId).then(permissions => setUserPermissions(permissions));
}, [userId]);
if (!userPermissions) return <p>Loading...</p>;
const dynamicRole = auth.build(userPermissions);
return (
<div className="user-permission-manager">
<h2>User Custom Permissions</h2>
<dynamicRole.Show resources="posts" actions="create">
<p>User can create posts</p>
</dynamicRole.Show>
<dynamicRole.Show resources="comments" actions="moderate">
<p>User can moderate comments</p>
</dynamicRole.Show>
<h3>Detailed Permissions:</h3>
<ul>
<li>Edit any post: {dynamicRole.can('posts', 'update') ? 'Yes' : 'No'}</li>
<li>Delete comments: {dynamicRole.can('comments', 'delete') ? 'Yes' : 'No'}</li>
<li>Ban users: {dynamicRole.can('users', 'ban') ? 'Yes' : 'No'}</li>
</ul>
{dynamicRole.can('system', 'configureSite') && (
<button onClick={() => openSiteConfiguration()}>Configure Site</button>
)}
</div>
);
}
TypeScript Support
Enhanced TypeScript example with custom types:
import { Role, Roles, TRoleOptions } from '@iamjs/core';
import { useAuthorization, TShowProps, TBuildShowProps } from '@iamjs/react';
interface BlogResources {
posts: string;
comments: string;
users: string;
system: string;
}
interface BlogActions {
posts: 'create' | 'read' | 'update' | 'delete' | 'publish' | 'unpublish';
comments: 'create' | 'read' | 'update' | 'delete' | 'moderate';
users: 'create' | 'read' | 'update' | 'delete' | 'ban' | 'unban';
system: 'configureSite' | 'viewAnalytics';
}
type BlogRoles = {
reader: Role<TRoleOptions<BlogResources, BlogActions>>;
author: Role<TRoleOptions<BlogResources, BlogActions>>;
admin: Role<TRoleOptions<BlogResources, BlogActions>>;
};
const schema = createSchema<BlogRoles>({ /* ... role definitions ... */ });
function BlogComponent() {
const auth = useAuthorization<BlogRoles>(schema);
// TypeScript will provide autocomplete and type checking
const canCreatePost: boolean = auth.can('author', 'posts', 'create');
const canModerateComments: boolean = auth.can('admin', 'comments', 'moderate');
const ShowAdminControls: React.FC<TShowProps<BlogRoles>> = ({ children }) => (
<auth.Show
role="admin"
resources={['posts', 'comments']}
actions={['delete', 'moderate']}
strict={true}
>
{children}
</auth.Show>
);
return (
<div>
{canCreatePost && <CreatePostButton />}
{canModerateComments && <ModerateCommentsPanel />}
<ShowAdminControls>
<AdminControlPanel />
</ShowAdminControls>
</div>
);
}
Advanced Usage Patterns
Composing Multiple Permissions
function ComplexPermissionCheck({ user, post }) {
const auth = useAuthorization(schema);
const canEditPost = auth.can(user.role, 'posts', 'update');
const isPostAuthor = user.id === post.authorId;
const canModerateComments = auth.can(user.role, 'comments', 'moderate');
const canPerformAction = canEditPost && (isPostAuthor || canModerateComments);
return (
<div>
{canPerformAction ? (
<EditPostForm post={post} />
) : (
<p>You don't have permission to edit this post.</p>
)}
</div>
);
}
Using with React Context
import React from 'react';
import { useAuthorization } from '@iamjs/react';
const AuthContext = React.createContext(null);
export function AuthProvider({ children, schema }) {
const auth = useAuthorization(schema);
return <AuthContext.Provider value={auth}>{children}</AuthContext.Provider>;
}
export function useAuth() {
const context = React.useContext(AuthContext);
if (context === null) {
throw new Error('useAuth must be used within an AuthProvider');
}
return context;
}
// Usage
function App() {
return (
<AuthProvider schema={schema}>
<YourComponents />
</AuthProvider>
);
}
function ProtectedComponent() {
const auth = useAuth();
// Use auth methods here
}
Performance Optimized Usage Examples
Optimized useAuthorization Hook Usage
Use useMemo
to memoize the result of useAuthorization
to prevent unnecessary re-renders:
import React, { useMemo } from 'react';
import { useAuthorization } from '@iamjs/react';
function BlogPost({ post, currentUser }) {
const auth = useMemo(() => useAuthorization(schema), []);
const permissions = useMemo(() => ({
canEdit: auth.can(currentUser.role, 'posts', 'update'),
canDelete: auth.can(currentUser.role, 'posts', 'delete'),
canModerateComments: auth.can(currentUser.role, 'comments', 'moderate')
}), [currentUser.role, auth]);
return (
<div className="blog-post">
<h1>{post.title}</h1>
<p>{post.content}</p>
{permissions.canEdit && <EditButton postId={post.id} />}
{permissions.canDelete && <DeleteButton postId={post.id} />}
<CommentsSection postId={post.id} />
{permissions.canModerateComments && <ModerateCommentsPanel postId={post.id} />}
</div>
);
}
const EditButton = React.memo(({ postId }) => (
<button onClick={() => editPost(postId)}>Edit Post</button>
));
const DeleteButton = React.memo(({ postId }) => (
<button onClick={() => deletePost(postId)}>Delete Post</button>
));
Optimized Dynamic Role Building
Use useMemo
and useCallback
for optimized dynamic role building:
function UserPermissionManager({ userId }) {
const auth = useMemo(() => useAuthorization(schema), []);
const [userPermissions, setUserPermissions] = useState(null);
useEffect(() => {
fetchUserPermissions(userId).then(setUserPermissions);
}, [userId]);
const dynamicRole = useMemo(() => {
if (!userPermissions) return null;
return auth.build(userPermissions);
}, [auth, userPermissions]);
const openSiteConfiguration = useCallback(() => {
// Implementation
}, []);
if (!dynamicRole) return <p>Loading...</p>;
return (
<div className="user-permission-manager">
<h2>User Custom Permissions</h2>
<dynamicRole.Show resources="posts" actions="create">
<p>User can create posts</p>
</dynamicRole.Show>
<dynamicRole.Show resources="comments" actions="moderate">
<p>User can moderate comments</p>
</dynamicRole.Show>
<PermissionList dynamicRole={dynamicRole} />
{dynamicRole.can('system', 'configureSite') && (
<button onClick={openSiteConfiguration}>Configure Site</button>
)}
</div>
);
}
const PermissionList = React.memo(({ dynamicRole }) => (
<>
<h3>Detailed Permissions:</h3>
<ul>
<li>Edit any post: {dynamicRole.can('posts', 'update') ? 'Yes' : 'No'}</li>
<li>Delete comments: {dynamicRole.can('comments', 'delete') ? 'Yes' : 'No'}</li>
<li>Ban users: {dynamicRole.can('users', 'ban') ? 'Yes' : 'No'}</li>
</ul>
</>
));
Advanced Usage Patterns with Performance Optimization
Custom Hook for Permissions
Create a custom hook to encapsulate permission checks and memoize results:
function usePermissions(role) {
const auth = useMemo(() => useAuthorization(schema), []);
return useMemo(() => ({
canCreatePost: auth.can(role, 'posts', 'create'),
canEditPost: auth.can(role, 'posts', 'update'),
canDeletePost: auth.can(role, 'posts', 'delete'),
canModerateComments: auth.can(role, 'comments', 'moderate'),
}), [auth, role]);
}
function BlogComponent({ currentUser }) {
const permissions = usePermissions(currentUser.role);
return (
<div>
{permissions.canCreatePost && <CreatePostButton />}
{permissions.canModerateComments && <ModerateCommentsPanel />}
<PostList permissions={permissions} />
</div>
);
}
const PostList = React.memo(({ permissions }) => {
// Render posts with appropriate action buttons based on permissions
});
Optimized Context Usage
Optimize the AuthContext usage with useMemo
and useCallback
:
import React, { useMemo, useCallback } from 'react';
import { useAuthorization } from '@iamjs/react';
const AuthContext = React.createContext(null);
export function AuthProvider({ children, schema }) {
const auth = useMemo(() => useAuthorization(schema), [schema]);
const contextValue = useMemo(() => ({
can: useCallback((...args) => auth.can(...args), [auth]),
Show: auth.Show,
build: useCallback((data) => auth.build(data), [auth]),
}), [auth]);
return <AuthContext.Provider value={contextValue}>{children}</AuthContext.Provider>;
}
export function useAuth() {
const context = React.useContext(AuthContext);
if (context === null) {
throw new Error('useAuth must be used within an AuthProvider');
}
return context;
}
Best Practices and Tips
-
Centralize Role Definitions: Keep your role definitions in a separate file for easy management.
-
Use Granular Permissions: Define specific permissions for different actions on resources.
-
Leverage TypeScript: Use TypeScript to catch permission-related errors early.
-
Combine with Authentication: Integrate @iamjs/react with your authentication system.
-
Memoize Permission Checks: For performance in large applications, consider memoizing permission check results.
-
Testing: Write unit tests for your permission logic.
-
Dynamic Permissions: Use the
build
method for runtime permission changes. -
Consistent Usage: Stick to either
can
method orShow
component throughout your app. -
Error Boundaries: Implement error boundaries to gracefully handle unauthorized access attempts.
-
Documentation: Keep your roles and permissions well-documented for team reference.