Headless React hooks for ABAC Policy Administration - bring your own UI
npm install @devcraft-ts/abac-admin-react> Headless React hooks for ABAC Policy Administration



Explore a fully functional demo application showcasing all hooks and features, including complete documentation for abac-engine integration and best practices.
@devcraft-ts/abac-admin-react provides headless React hooks for managing ABAC (Attribute-Based Access Control) policies, attributes, and audit logs. Built on top of @devcraft-ts/abac-admin-core, these hooks give you full control over your UI while handling all the data fetching and state management logic.
ā
Headless by Design - No UI components, full control over your interface
ā
Simple & Lightweight - Built with native React hooks (useState, useEffect)
ā
Type-Safe - Full TypeScript support with comprehensive types
ā
Framework Agnostic Data Layer - Works with any UI library or component framework
ā
Optional TanStack Query Support - Use with or without external data fetching libraries
ā
Comprehensive Hook Set - Policy, Attribute, and Audit management
ā
Optimistic Updates - Built-in state management for smooth UX
ā
Built on abac-engine - Leverages the official abac-engine for policy evaluation
``bash`
npm install @devcraft-ts/abac-admin-react @devcraft-ts/abac-admin-core zod react
`bash`
yarn add @devcraft-ts/abac-admin-react @devcraft-ts/abac-admin-core zod react
`bash`
pnpm add @devcraft-ts/abac-admin-react @devcraft-ts/abac-admin-core zod react
`tsx
import { ABACProvider } from "@devcraft-ts/abac-admin-react";
function App() {
return (
baseURL: "/api/abac",
headers: {
Authorization: Bearer ${yourAuthToken},`
},
}}
>
);
}
`tsx
import { usePolicies } from "@devcraft-ts/abac-admin-react";
function PolicyList() {
const {
policies,
isLoading,
error,
createPolicy,
deletePolicy,
activatePolicy,
} = usePolicies();
if (isLoading) return
return (
{policy.description}
API Reference
$3
#### ABACProvider
Provides ABAC client instance to all child components.
`tsx
`Props:
-
config: ABACAdminConfig - Client configuration
- baseURL: string - API base URL (required)
- headers?: Record - Custom headers (optional)
- timeout?: number - Request timeout in ms (optional, default: 30000)#### useABACClient
Access the ABAC client instance directly.
`tsx
const client = useABACClient();
`---
Policy Hooks
$3
Manage multiple policies with full CRUD operations.
`tsx
const {
policies, // Policy[] - Array of policies
isLoading, // boolean - Loading state
error, // Error | null - Error state
refetch, // () => Promise - Refetch policies
createPolicy, // (policy: PolicyInput) => Promise
updatePolicy, // (id: string, policy: PolicyUpdate) => Promise
deletePolicy, // (id: string) => Promise
activatePolicy, // (id: string) => Promise
deactivatePolicy // (id: string) => Promise
} = usePolicies(filters?);
`Example:
`tsx
function PolicyManager() {
const { policies, createPolicy, updatePolicy, isLoading } = usePolicies({
isActive: true,
category: "document",
}); const handleCreate = async () => {
await createPolicy({
policyId: "new-policy",
version: "1.0.0",
effect: "PERMIT",
description: "Allow document access",
conditions: {
type: "equals",
left: { category: "subject", key: "role" },
right: "admin",
},
isActive: true,
category: "document",
tags: ["document", "read"],
createdBy: "user-123",
});
};
return (
{policies.map((p) => (
{p.policyId}
))}
);
}
`$3
Fetch a single policy by ID.
`tsx
const {
policy, // Policy | null - The policy
isLoading, // boolean - Loading state
error, // Error | null - Error state
refetch, // () => Promise - Refetch policy
} = usePolicy(policyId);
`Example:
`tsx
function PolicyDetails({ policyId }: { policyId: string }) {
const { policy, isLoading, error } = usePolicy(policyId); if (isLoading) return
Loading...;
if (error) return Error: {error.message};
if (!policy) return Policy not found; return (
{policy.policyId}
{policy.description}
Status: {policy.isActive ? "Active" : "Inactive"}
Version: {policy.version}
);
}
`$3
Test policy evaluation against a request.
`tsx
const {
testPolicy, // (request: PolicyTestRequest) => Promise
isLoading, // boolean - Loading state
error, // Error | null - Error state
result, // PolicyTestResult | null - Test result
} = usePolicyTest();
`Example:
`tsx
function PolicyTester({ policy }: { policy: Policy }) {
const { testPolicy, isLoading, result } = usePolicyTest(); const handleTest = async () => {
const testResult = await testPolicy({
policy,
request: {
subject: { role: "admin", department: "engineering" },
action: { type: "read" },
resource: { type: "document", id: "doc-123" },
},
});
console.log("Decision:", testResult.decision);
};
return (
{result && (
Decision: {result.decision}
Reason: {result.reason}
)}
);
}
`$3
Fetch all versions of a policy.
`tsx
const {
versions, // Policy[] - Array of policy versions
isLoading, // boolean - Loading state
error, // Error | null - Error state
refetch, // () => Promise - Refetch versions
} = usePolicyVersions(policyId);
`---
Attribute Hooks
$3
Manage resource attributes with full CRUD operations.
`tsx
const {
attributes, // Record - All attributes for resource
isLoading, // boolean - Loading state
error, // Error | null - Error state
refetch, // () => Promise - Refetch attributes
setAttribute, // (key: string, value: any) => Promise
deleteAttribute, // (key: string) => Promise
bulkSetAttributes, // (attrs: Record) => Promise
bulkDeleteAttributes, // (keys: string[]) => Promise
} = useAttributes(resourceType, resourceId);
`Example:
`tsx
function UserAttributeManager({ userId }: { userId: string }) {
const {
attributes,
setAttribute,
deleteAttribute,
bulkSetAttributes,
isLoading,
} = useAttributes("user", userId); const makeAdmin = () => setAttribute("role", "admin");
const updateMultiple = () =>
bulkSetAttributes({
role: "senior-engineer",
department: "engineering",
level: 5,
});
return (
User Attributes
{Object.entries(attributes).map(([key, value]) => (
{key}: {JSON.stringify(value)}
))}
);
}
`$3
Fetch a single attribute value.
`tsx
const {
value, // any - Attribute value
isLoading, // boolean - Loading state
error, // Error | null - Error state
refetch, // () => Promise - Refetch attribute
} = useAttribute(resourceType, resourceId, key);
`$3
View attribute change history.
`tsx
const {
history, // AttributeValue[] - History of changes
isLoading, // boolean - Loading state
error, // Error | null - Error state
refetch // () => Promise - Refetch history
} = useAttributeHistory(resourceType, resourceId, key?);
`Example:
`tsx
function AttributeHistory({ userId }: { userId: string }) {
const { history, isLoading } = useAttributeHistory("user", userId, "role"); return (
Role Change History
{history.map((entry) => (
{entry.value} at {new Date(entry.timestamp).toLocaleString()}
))}
);
}
`$3
Compare attributes between two resources.
`tsx
const {
comparison, // ComparisonResult - Comparison data
isLoading, // boolean - Loading state
error, // Error | null - Error state
refetch, // () => Promise - Refetch comparison
} = useAttributeComparison(resourceType, resourceId1, resourceId2);
`---
Audit Hooks
$3
Fetch audit logs with filters.
`tsx
const {
entries, // AuditLogEntry[] - Log entries
total, // number - Total count
hasMore, // boolean - More entries available
isLoading, // boolean - Loading state
error, // Error | null - Error state
refetch // () => Promise - Refetch logs
} = useAuditLog(filters?);
`Example:
`tsx
function AuditLog() {
const { entries, total, hasMore, isLoading } = useAuditLog({
entityType: "policy",
action: "UPDATE",
startDate: "2024-01-01T00:00:00Z",
limit: 50,
}); return (
Audit Log ({total} entries)
{entries.map((entry) => (
{entry.action} on {entry.entityType}
By {entry.userId} at {new Date(entry.timestamp).toLocaleString()}
))}
{hasMore && }
);
}
`$3
Fetch all audit entries for a specific entity.
`tsx
const {
entries, // AuditLogEntry[] - History entries
isLoading, // boolean - Loading state
error, // Error | null - Error state
refetch // () => Promise - Refetch history
} = useEntityHistory(entityType, entityId, limit?);
`$3
Fetch all audit entries for a specific user.
`tsx
const {
entries, // AuditLogEntry[] - User activity entries
total, // number - Total count
hasMore, // boolean - More entries available
isLoading, // boolean - Loading state
error, // Error | null - Error state
refetch // () => Promise - Refetch activity
} = useUserActivity(userId, options?);
`$3
Fetch audit statistics.
`tsx
const {
statistics, // AuditStatistics - Statistics data
isLoading, // boolean - Loading state
error, // Error | null - Error state
refetch // () => Promise - Refetch statistics
} = useAuditStatistics(startDate?, endDate?);
`$3
Fetch recent audit activity.
`tsx
const {
entries, // AuditLogEntry[] - Recent entries
isLoading, // boolean - Loading state
error, // Error | null - Error state
refetch // () => Promise - Refetch activity
} = useRecentActivity(limit?);
`---
Advanced Usage
$3
`tsx
function MyComponent() {
const { policies, error, refetch } = usePolicies(); useEffect(() => {
if (error) {
// Log to error tracking service
console.error("Policy fetch failed:", error);
// Show toast notification
showToast("Failed to load policies", "error");
// Retry after delay
setTimeout(() => refetch(), 5000);
}
}, [error, refetch]);
return
{/ ... /};
}
`$3
`tsx
function App() {
const [authToken, setAuthToken] = useState(null); return (
config={{
baseURL: "/api/abac",
headers: authToken
? {
Authorization:
Bearer ${authToken},
}
: {},
}}
>
);
}
`$3
All mutation hooks (create, update, delete) immediately update local state for instant UI feedback, then handle errors by reverting if the API call fails.
`tsx
function QuickToggle({ policyId }: { policyId: string }) {
const { activatePolicy, deactivatePolicy } = usePolicies();
const [isActive, setIsActive] = useState(false); const toggle = async () => {
// Optimistically update UI
setIsActive(!isActive);
try {
if (isActive) {
await deactivatePolicy(policyId);
} else {
await activatePolicy(policyId);
}
} catch (error) {
// Revert on error
setIsActive(isActive);
console.error("Toggle failed:", error);
}
};
return (
);
}
`$3
While not required, you can use these hooks alongside TanStack Query:
`tsx
import { useQuery } from "@tanstack/react-query";
import { useABACClient } from "@devcraft-ts/abac-admin-react";
import { PolicyService } from "@devcraft-ts/abac-admin-core";function PolicyListWithQuery() {
const client = useABACClient();
const { data, isLoading } = useQuery({
queryKey: ["policies"],
queryFn: async () => {
const service = new PolicyService(client);
return service.list();
},
});
return
{/ ... /};
}
`---
TypeScript Support
All hooks are fully typed with comprehensive TypeScript definitions:
`tsx
import type {
UsePoliciesResult,
UseAttributesResult,
UseAuditLogResult,
Policy,
PolicyInput,
PolicyUpdate,
AttributeValue,
AuditLogEntry,
} from "@devcraft-ts/abac-admin-react";
`---
Best Practices
$3
`tsx
// ā
Good
function App() {
return (
);
}// ā Bad - Provider too deep in tree
function SomeComponent() {
return (
);
}
`$3
`tsx
// ā
Good
function PolicyList() {
const { policies, isLoading, error } = usePolicies(); if (isLoading) return ;
if (error) return ;
return
{/ ... /};
}// ā Bad - No loading/error handling
function PolicyList() {
const { policies } = usePolicies();
return
{policies.map(/ ... /)};
}
`$3
`tsx
// ā
Good - Filter server-side
const { policies } = usePolicies({
isActive: true,
category: "document",
});// ā Bad - Filter client-side
const { policies } = usePolicies();
const activePolicies = policies.filter((p) => p.isActive);
``---
For complete working examples, see:
- Next.js Headless Example
- Custom UI Example
- With TanStack Query
---
- @devcraft-ts/abac-admin-core - Framework-agnostic core (required)
- @devcraft-ts/abac-admin-nextjs - Next.js server utilities
- @devcraft-ts/abac-admin-react-ui - Pre-built UI components (optional)
---
Contributions are welcome! Please read our Contributing Guide.
---
MIT Ā© [astralstriker]
---
- Documentation
- GitHub Issues
- Discord Community
---
Built with ā¤ļø for developers who want full control over their ABAC admin UI.