Usage Guide A comprehensive guide to using @iamjs/react for role-based access control in React applications.
@ 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.
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').
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 } );
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 >
);
}
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 >
);
}
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 >
);
}
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 >
);
}
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>
);
}
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 >
);
}
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>
);
}
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 >
);
}
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
}
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 >
));
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 >
</>
));
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
} );
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;
}
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 or Show
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.