@ldauth/react
v0.10.1
Published
React permission checking library for the auth system
Maintainers
Readme
@ldauth/react
A React library for fine-grained permission checking in applications using our authentication system. This library provides components, hooks, and utilities to conditionally render UI based on user permissions.
Installation
pnpm add @ldauth/react react-oidc-contextQuick Start
1. Wrap your app with PermissionProvider
import { AuthProvider } from 'react-oidc-context';
import { PermissionProvider } from '@ldauth/react';
const oidcConfig = {
authority: 'http://localhost:7844',
client_id: 'admin-panel',
redirect_uri: 'http://localhost:7802/callback',
// ... other OIDC config
};
function App() {
return (
<AuthProvider {...oidcConfig}>
<PermissionProvider
apiUrl="http://localhost:7801"
clientId="admin-panel"
autoFetch={true}
cacheDuration={5 * 60 * 1000} // 5 minutes
>
<YourApp />
</PermissionProvider>
</AuthProvider>
);
}2. Use permission components
import { CanAccess, RequireRole } from '@ldauth/react';
function MyComponent() {
return (
<div>
{/* Show delete button only if user has permission */}
<CanAccess resource="users" action="delete">
<button>Delete User</button>
</CanAccess>
{/* Show admin panel only for admin role */}
<RequireRole role="admin">
<AdminPanel />
</RequireRole>
{/* With fallback */}
<CanAccess
resource="settings"
action="update"
fallback={<p>Read-only access</p>}
>
<SettingsForm />
</CanAccess>
</div>
);
}3. Use hooks for permissions and user information
import {
usePermission,
useRole,
useUser,
useUserPicture,
useUserGroups
} from '@ldauth/react';
function ItemList() {
// Get user information from ID token (zero backend calls)
const user = useUser();
const avatarUrl = useUserPicture();
const groups = useUserGroups();
// Check permissions
const { allowed: canDeleteItems, isLoading: deleteLoading } = usePermission('items', 'delete');
const { allowed: canCreateItems } = usePermission('items', 'create');
const isManager = useRole('manager');
const handleDelete = (itemId) => {
if (!canDeleteItems) {
alert('You do not have permission to delete items');
return;
}
// ... delete logic
};
return (
<div>
{/* User profile section */}
<div className="user-profile">
{avatarUrl && <img src={avatarUrl} alt="Avatar" />}
<span>{user?.name || user?.email}</span>
<span>Groups: {groups.join(', ')}</span>
</div>
{/* Actions based on permissions */}
{canCreateItems && (
<button>Create Item</button>
)}
{items.map(item => (
<div key={item.id}>
{item.name}
{canDeleteItems && (
<button onClick={() => handleDelete(item.id)}>Delete</button>
)}
</div>
))}
</div>
);
}Components
<CanAccess>
Conditionally renders children based on a permission check.
<CanAccess
resource="users"
action="delete"
profile="__default" // optional
fallback={<span>No permission</span>} // optional
loadingFallback={<span>Checking permissions...</span>} // optional
>
<DeleteButton />
</CanAccess><CanAccessMultiple>
Check multiple permissions with AND/OR logic.
<CanAccessMultiple
permissions={[
{ resource: 'users', action: 'read' },
{ resource: 'groups', action: 'read' }
]}
requireAll={true} // true = AND, false = OR
>
<UserGroupManager />
</CanAccessMultiple><RequireRole>
Conditionally renders based on role membership.
<RequireRole role="admin" fallback={<Unauthorized />}>
<AdminDashboard />
</RequireRole><PermissionGate>
Custom permission logic.
<PermissionGate
check={perms => perms.hasRole('admin') || perms.hasPermission('users', 'manage')}
>
<AdvancedFeature />
</PermissionGate>Hooks
Core Hooks
useAuth()
Access the underlying react-oidc-context authentication state without importing another package. This is a direct re-export of useAuth from react-oidc-context.
import { useAuth } from '@ldauth/react';
function IdentityBadge() {
const auth = useAuth();
if (!auth.isAuthenticated) {
return <button onClick={() => auth.signinRedirect()}>Sign in</button>;
}
return (
<div>
<span>{auth.user?.profile?.email}</span>
<button onClick={() => auth.signoutRedirect()}>Logout</button>
</div>
);
}usePermissions()
Access the permission context directly.
const {
permissions, // Current permission evaluation
loading, // Loading state
error, // Error state
hasPermission, // Check specific permission
hasRole, // Check role membership
refresh // Refresh permissions
} = usePermissions();usePermission(resource, action, profile?)
Check a single permission. Returns an object with permission status and loading state.
const { allowed, isLoading } = usePermission('users', 'delete');
// Or destructure with custom names:
const { allowed: canDelete, isLoading: deleteLoading } = usePermission('users', 'delete');useRole(role)
Check role membership.
const isAdmin = useRole('admin');User Information Hooks
useUser()
Access all authenticated user information from the ID token.
const user = useUser();
if (user) {
console.log(user.email); // [email protected]
console.log(user.name); // John Doe
console.log(user.picture); // https://api.example.com/avatars/123.jpg
console.log(user.groups); // ["admin", "users"]
console.log(user.sub); // User ID
}useUserPicture()
Get the current user's profile picture URL. Returns null if not authenticated or no picture available.
const avatarUrl = useUserPicture();
return (
<div>
{avatarUrl ? (
<img src={avatarUrl} alt="User avatar" className="avatar" />
) : (
<DefaultAvatarIcon />
)}
</div>
);useUserGroups()
Get the current user's group memberships. Returns an empty array if not authenticated.
const groups = useUserGroups();
const isAdmin = groups.includes('admin');
const isModerator = groups.includes('moderator');
return (
<div>
<p>Your groups: {groups.join(', ')}</p>
{isAdmin && <AdminBadge />}
</div>
);Note: All user information hooks read from the cached ID token (zero backend calls). The data is automatically refreshed when the user re-authenticates.
Utility Hooks
useFeatureFlags()
Create feature flags based on permissions.
const features = useFeatureFlags({
canEditUsers: { resource: 'users', action: 'update' },
canDeleteUsers: { resource: 'users', action: 'delete' },
canViewAudit: { resource: 'audit', action: 'read' }
});
if (features.canEditUsers) {
// Show edit button
}useFilterByPermission()
Filter items based on permissions.
const menuItems = [
{ label: 'Users', resource: 'users', action: 'read', path: '/users' },
{ label: 'Groups', resource: 'groups', action: 'read', path: '/groups' },
{ label: 'Audit', resource: 'audit', action: 'read', path: '/audit' }
];
const visibleMenuItems = useFilterByPermission(menuItems);
// Returns only items the user has permission to accessuseRequirePermission()
Throw an error if permission check fails (useful for route protection).
function ProtectedPage() {
useRequirePermission('admin', 'access');
// Will throw if user doesn't have permission
return <AdminContent />;
}HOC (Higher Order Component)
withPermission()
Wrap a component with permission check.
const ProtectedUserList = withPermission(UserList, {
resource: 'users',
action: 'read',
fallback: <div>Access denied</div>
});Integration Example
Define Your Application's Permissions
Each application defines its own permission structure based on its needs:
// myAppPermissions.ts
const MY_APP_FEATURES = {
// Define feature flags based on YOUR application's resources
canViewDashboard: { resource: 'dashboard', action: 'view' },
canEditSettings: { resource: 'settings', action: 'edit' },
canManageContent: { resource: 'content', action: 'manage' },
// ... whatever permissions make sense for your app
};
function useMyAppFeatures() {
return useFeatureFlags(MY_APP_FEATURES);
}Use in Navigation
function AppNavigation() {
// Define navigation based on YOUR app's permissions
const { allowed: canViewDashboard } = usePermission('dashboard', 'view');
const { allowed: canViewReports } = usePermission('reports', 'view');
const { allowed: canManageSettings } = usePermission('settings', 'manage');
return (
<nav>
{canViewDashboard && (
<Link to="/">Dashboard</Link>
)}
{canViewReports && (
<Link to="/reports">Reports</Link>
)}
{canManageSettings && (
<Link to="/settings">Settings</Link>
)}
</nav>
);
}Protect Routes
import { Routes, Route } from 'react-router-dom';
function AdminRoutes() {
return (
<Routes>
<Route path="/" element={
<CanAccess resource="dashboard" action="read" fallback={<Unauthorized />}>
<Dashboard />
</CanAccess>
} />
<Route path="/users/*" element={
<CanAccess resource="users" action="read" fallback={<Unauthorized />}>
<UserRoutes />
</CanAccess>
} />
<Route path="/settings" element={
<RequireRole role="admin" fallback={<Unauthorized />}>
<Settings />
</RequireRole>
} />
</Routes>
);
}Performance Considerations
Caching: Permissions are cached by default for 5 minutes. Adjust
cacheDurationin PermissionProvider.Batch Checks: Use
CanAccessMultipleoruseAllPermissionsfor checking multiple permissions at once.Memoization: Permission check functions are memoized to prevent unnecessary re-renders.
Lazy Loading: Set
autoFetch={false}and manually callrefresh()when needed.
Error Handling
<PermissionProvider
apiUrl="..."
clientId="..."
onError={(error) => {
console.error('Permission check failed:', error);
// Show notification to user
}}
>
{children}
</PermissionProvider>TypeScript Support
The library is fully typed and provides type-safe component factories for schema-based type safety.
Type-Safe Components with Schema
Create typed components that provide full autocomplete for your permission schema:
import {
createUsePermission,
createCanAccess,
createCanAccessMultiple,
createPermissionGate
} from '@ldauth/react';
// Define your permission schema
interface MyAppSchema {
resources: {
user: { actions: ['create', 'read', 'update', 'delete'] };
post: { actions: ['create', 'read', 'publish', 'archive'] };
admin: { actions: ['access', 'manage'] };
};
}
// Create typed versions
const useTypedPermission = createUsePermission<MyAppSchema>();
const TypedCanAccess = createCanAccess<MyAppSchema>();
const TypedCanAccessMultiple = createCanAccessMultiple<MyAppSchema>();
const TypedPermissionGate = createPermissionGate<MyAppSchema>();
// Usage with full autocomplete support
function MyComponent() {
// Typed hook with autocomplete
const { allowed: canCreateUser, isLoading } = useTypedPermission('user', 'create');
return (
<div>
{/* Single permission with autocomplete */}
<TypedCanAccess
resource="user"
action="delete"
fallback={<div>Cannot delete users</div>}
loadingFallback={<div>Checking permissions...</div>}
>
<DeleteUserButton />
</TypedCanAccess>
{/* Multiple permissions with type safety */}
<TypedCanAccessMultiple
permissions={[
{ resource: "user", action: "read" },
{ resource: "post", action: "read" }
]}
requireAll={true}
fallback={<div>Need both user and post read access</div>}
>
<AdminDashboard />
</TypedCanAccessMultiple>
{/* Custom logic with type-safe context */}
<TypedPermissionGate
check={(client, loading) => {
if (loading) return false;
return client?.hasRole("admin") || client?.hasPermission("admin", "access");
}}
fallback={<div>Admin access required</div>}
>
<AdminPanel />
</TypedPermissionGate>
</div>
);
}Basic Types
Import types as needed:
import type {
Permission,
PermissionCheck,
PermissionEvaluation,
PermissionProviderConfig,
PermissionSchemaBase
} from '@ldauth/react';Best Practices
Use specific checks: Prefer
usePermission('users', 'delete')overuseRole('admin')Provide fallbacks: Always provide fallback UI for users without permissions
Handle loading states: Use
loadingFallbackto show different content while checking permissionsGroup related checks: Use
useFeatureFlags()to group related permission checksCache appropriately: Balance between fresh data and performance
Test with different roles: Always test your UI with different permission levels
License
MIT
