ACL Protection Rules
| Field | Value |
|---|---|
| Version | 1.0.0 |
| Last Updated | January 15, 2025 |
| Owner | Frontend Team |
| Status | ✅ Current |
| Type | Standard |
ACL protection is currently only for Whitelabel modules. Before implementing a new module or adding actions, confirm whether ACL is required.
Rationale
Access Control List (ACL) protection ensures that users can only access and modify resources they have permission to interact with. This standard provides consistent patterns for implementing ACL across all Whitelabel modules, ensuring:
- Security: Unauthorized users cannot access or modify protected resources
- Consistency: All modules follow the same protection patterns
- Maintainability: Clear, predictable implementation patterns make code easier to understand and maintain
- User Experience: Users see appropriate UI elements based on their permissions
Permission Format
| Protection Type | Format | Example |
|---|---|---|
| Route protection | view:{resource} | view:articles, view:branches |
| Action protection | control:{resource} | control:articles, control:branches |
Protection Patterns
1. Route Protection
Add aclGrants to the route definition in pathEntry.tsx:
aclProps: { aclGrants: ['view:articles'] }
2. Direct Action Buttons (Delete, Publish, etc.)
Behavior: Hide the button if user doesn't have control:{resource} permission.
Preferred: ACLBoundary Component
<ACLBoundary
aclGrants={['control:articles']}
aclUnAuthProps={{ hide: true }}
>
<Button onClick={handleDelete}>Delete</Button>
</ACLBoundary>
Alternative: useACL Hook
Only use when hiding with ACL is complex (e.g., complex conditional logic):
const { isAllowed } = useACL(['control:articles']);
if (!isAllowed) return null;
return <Button onClick={handleDelete}>Delete</Button>;
3. Edit Actions (Open Drawer)
Behavior:
- ✅ Always show the edit button (users can view data)
- ✅ Drawer opens normally and prefills data
- ❌ Hide submit button if user doesn't have
control:{resource}permission
Preferred: ACLBoundary for Drawer Footer
return (
<Drawer>
<Drawer.Content>...</Drawer.Content>
<ACLBoundary
aclGrants={['control:articles']}
aclUnAuthProps={{ hide: true }}
>
<Drawer.Footer onSubmit={handleSubmit} />
</ACLBoundary>
</Drawer>
);
Alternative: useACL Hook
const { isAllowed } = useACL(['control:articles']);
return (
<Drawer>
<Drawer.Content>...</Drawer.Content>
{isAllowed && <Drawer.Footer onSubmit={handleSubmit} />}
</Drawer>
);
4. Add/Create Buttons
Behavior: Hide the button if user doesn't have control:{resource} permission.
Preferred: ACLBoundary Component
<ACLBoundary
aclGrants={['control:articles']}
aclUnAuthProps={{ hide: true }}
>
<AddButton onClick={handleAdd} />
</ACLBoundary>
Alternative: useACL Hook
const { isAllowed } = useACL(['control:articles']);
if (!isAllowed) return null;
return <AddButton onClick={handleAdd} />;
5. Page Headers
Behavior: If the header only contains an Add/Create button and it's hidden, hide the entire header.
Check permission at header level:
const { isAllowed } = useACL(['control:articles']);
if (!isAllowed) return null;
return <PageHeader><AddButton onClick={handleAdd} /></PageHeader>;
6. EmptyState Actions
Behavior:
- ✅ Always show the EmptyState component (title, subtitle, etc.)
- ❌ Hide only the action button if user doesn't have
control:{resource}permission
Do NOT wrap the entire EmptyState with ACLBoundary — this would hide the entire empty state.
Correct Implementation
const { isAllowed: hasControlPermission } = useACL(['control:articles']);
return (
<EmptyState
title="No articles"
subtitle="Get started by adding your first article"
{...(hasControlPermission && {
action: {
onClick: handleAdd,
label: 'Add Article',
},
})}
data={articles}
/>
);
7. Inputs/Form Controls
Behavior: Always use hide: true, never disabled: true.
<ACLBoundary
aclGrants={['control:articles']}
aclUnAuthProps={{ hide: true }}
>
<Input {...props} />
</ACLBoundary>
Implementation Checklist
For each module, verify:
- Route protected with
view:{resource} - Delete buttons hidden (ACLBoundary preferred)
- Edit buttons always visible
- Drawer submit buttons hidden (ACLBoundary preferred)
- Add buttons hidden (ACLBoundary preferred)
- Page headers hidden if only Add button and no permission
- EmptyState always visible, action button hidden via useACL hook
Quick Reference Table
| Element | Visibility | Protection Method |
|---|---|---|
| Route | Based on view:{resource} | aclGrants in route config |
| Delete Button | Hidden without permission | ACLBoundary (preferred) |
| Edit Button | Always visible | None |
| Drawer Submit | Hidden without permission | ACLBoundary (preferred) |
| Add Button | Hidden without permission | ACLBoundary (preferred) |
| Header (Add only) | Hidden without permission | useACL hook |
| EmptyState | Always visible | — |
| EmptyState Action | Hidden without permission | useACL hook |
| Form Inputs | Hidden without permission | ACLBoundary with hide: true |
When to Use Each Approach
Use ACLBoundary When:
- Protecting JSX elements directly
- Simple show/hide logic
- Cleaner, more declarative code is preferred
Use useACL Hook When:
- Complex conditional logic is required
- Working with dynamic arrays
- Conditionally including object properties (like EmptyState actions)
- Need to check permission in non-JSX code
Important Notes
Always use hide: true in aclUnAuthProps to hide elements. Never use disabled: true for ACL-based protection.
For form inputs protected by ACL, always use hide: true — disabled inputs create confusion about why data can't be edited.
Change Log
| Version | Date | Changes | Author |
|---|---|---|---|
| 1.0.0 | 2025-01-15 | Initial version | Frontend Team |
Related Documentation
- FE/BE Communication Requirements - API communication guidelines
- Development Environment Setup - Set up your development environment
- Welcome Guide - Onboarding guide for new team members
- Documentation Guidelines - How to contribute to documentation