guardify 1.2.0
guardify: ^1.2.0 copied to clipboard
A Dart code generation package for Role-Based UI Access Control in Flutter widgets.
Guardify π‘οΈ #
A powerful, type-safe Role-Based UI Access Control (RBAC) and Permission-Based Access Control (PBAC) framework for Flutter apps, offering both Annotation-Based Code Generation and Zero-Boilerplate Runtime Widgets.
π Security Best Practices & Architectural Notice #
Important
Client-Side UI Gating vs. Server-Side Enforcement (Defense in Depth)
Guardify is designed for the Presentation Layer (UI/UX):
- It prevents unauthorized UI components from rendering or executing client-side callbacks (
onPressed,onTap). - It delivers smooth, clean UX by hiding restricted buttons, displaying disabled states with lock badges, and handling route redirects before screens mount.
However, client-side code runs on user-controlled hardware and can theoretically be inspected or manipulated:
- Never rely exclusively on client-side UI gating for data security.
- Server-Side Enforcement is Mandatory: Sensitive data operations, database queries, and mutation endpoints (e.g. DELETE / POST API calls) must always be authenticated and authorized on the backend server or cloud security layer (e.g., Firebase Security Rules, Supabase Row-Level Security (RLS), Node.js / Go / Spring Boot JWT claim validators).
- Verified Claims: Always supply
currentRole,currentRoles, orcurrentPermissionstoGuardifyScopefrom cryptographically verified authentication sources (such as verified JWT token claims, Firebase Auth Custom Claims, or Auth0 access tokens). - Secure by Default: Guardify follows the principle of least privilegeβif roles or permissions do not match, or if
GuardifyScopeis missing from the tree, access is safely denied by default.
β¨ Features #
- π‘οΈ Dual-Mode Access Control:
- Runtime Widget (
RoleGate/SecuredFeature): Protect any Flutter or 3rd-party widget on-the-fly with Zerobuild_runnerrequired. - Code Generation (
@Secured): Type-safe, component-level security policies declared on widget classes.
- Runtime Widget (
- β‘ Zero Prop-Drilling: Ambient role & permission state resolution via
GuardifyScope(InheritedWidget). - π Reactive State Binding: Connect directly to
ValueListenable,ChangeNotifier, orStreamusingGuardifyScope.listenableorGuardifyScope.streamfor automatic rebuilds on auth changes. - π¨ Flexible Fallback UI Strategies:
FallbackType.hide(default): Silently hides unauthorized widgets (const SizedBox.shrink()).FallbackType.disabled: Disables user interactions (AbsorbPointer) with reduced opacity and optional lock overlay icon.FallbackType.scaffold: Renders a full "Access Denied" page for screens.FallbackType.text: Displays an inline error text widget.- Dynamic Fallback Builders: Pass
fallbackBuilder: (context, missingRoles, missingPerms) => ...locally on widgets or globally inGuardifyScope.
- π₯ Multi-Role & PBAC Support: Configure roles and fine-grained permissions (
requireAll: true/false). - π§ Route Guard for Navigation: Easily secure routes with
GuardifyRouteGuardorguardifyRedirect(compatible withgo_routerand Navigator). - π Custom Permission Checker: Plug in custom auth callbacks (Firebase Auth, JWT claims, dynamic rules).
- π BuildContext Extensions: Query authorization directly in code using
context.hasRole('admin')orcontext.hasPermission('users:delete'). - π‘οΈ Variable Shadowing Immunity: Code generator safely isolates internal variables to avoid any parameter collisions.
π Getting Started #
Add Dependencies #
Add guardify to your pubspec.yaml:
dependencies:
flutter:
sdk: flutter
guardify: ^1.2.0
dev_dependencies:
build_runner: ^2.4.0 # Optional: only needed if using @Secured code generation
Run flutter pub get to install.
π» Usage #
1. Wrap Your App with GuardifyScope #
Wrap your app or widget tree with GuardifyScope to supply active user roles and permissions:
void main() {
runApp(
GuardifyScope(
currentRole: 'admin',
currentPermissions: const {'users:delete', 'reports:export'},
child: const MyApp(),
),
);
}
Reactive Auth Binding with GuardifyScope.listenable
Automatically rebuilds descendant scope widgets whenever your auth notifier changes (no setState required):
GuardifyScope.listenable(
listenable: authStateNotifier, // ValueNotifier<String?>
child: const MyApp(),
)
2. Choose Your Gating Mode #
Option A: Zero-Boilerplate Runtime Widget (RoleGate)
No code generation required! Guard any widget directly:
// 1. Silently hide for unauthorized users
RoleGate(
allowedRoles: const ['admin'],
child: FloatingActionButton(onPressed: () {}, child: const Icon(Icons.add)),
)
// 2. Disabled mode with lock overlay badge (e.g. Freemium / Pro features)
RoleGate(
allowedRoles: const ['premium'],
fallbackType: FallbackType.disabled,
lockBadge: const CircleAvatar(
backgroundColor: Colors.black87,
child: Icon(Icons.lock, color: Colors.amber),
),
child: ElevatedButton(onPressed: exportReport, child: const Text('Export PDF')),
)
// 3. Dynamic context-aware fallback builder
RoleGate(
allowedRoles: const ['editor'],
fallbackBuilder: (context, missingRoles, missingPerms) {
return UpgradeBanner(requiredRole: missingRoles.first);
},
child: const EditArticleToolbar(),
)
Option B: Annotation-Based Code Generation (@Secured)
Ideal for design systems, reusable button kits, and enterprise component governance:
part 'my_widgets.secured.g.dart';
@Secured(['admin', 'superadmin'], fallback: FallbackType.hide)
class DeleteUserButton extends StatelessWidget {
final String userId;
final VoidCallback onDelete;
const DeleteUserButton({super.key, required this.userId, required this.onDelete});
@override
Widget build(BuildContext context) => ElevatedButton(...);
}
Run build runner to generate .secured.g.dart:
dart run build_runner build --delete-conflicting-outputs
Then instantiate cleanly with zero prop-drilling:
SecuredDeleteUserButton(
userId: 'usr_100',
onDelete: () => deleteUser('usr_100'),
);
3. Route & Navigation Guards (go_router / Navigator) #
Prevent unauthorized users from entering protected screens at the router level:
GoRoute(
path: '/admin',
redirect: GuardifyRouteGuard(
allowedRoles: const ['admin'],
fallbackPath: '/unauthorized',
),
builder: (context, state) => const AdminScreen(),
);
Or perform an inline redirect check:
final redirect = guardifyRedirect(
context,
allowedRoles: const ['manager'],
fallbackPath: '/unauthorized',
);
if (redirect != null) {
// Handle redirect or show notification
}
4. BuildContext Extensions #
Use BuildContext helper extensions anywhere in your widget tree:
// Role checks
if (context.hasRole('admin')) { ... }
if (context.hasAnyRole(['admin', 'manager'])) { ... }
if (context.hasAllRoles(['manager', 'finance'])) { ... }
// Permission checks (PBAC)
if (context.hasPermission('users:delete')) { ... }
if (context.hasAnyPermission(['reports:view', 'reports:export'])) { ... }
if (context.hasAllPermissions(['posts:read', 'posts:write'])) { ... }
5. Fallback UI Strategies #
| Strategy | Behavior | Typical Use Case |
|---|---|---|
FallbackType.hide (default) |
Renders const SizedBox.shrink() |
Inline buttons, list actions |
FallbackType.disabled |
Renders with AbsorbPointer, reduced opacity, & optional lock badge |
Freemium / Pro features |
FallbackType.scaffold |
Full-screen "Access Denied" page with AppBar | Restricted screens / pages |
FallbackType.text |
Renders inline error text message | Form fields, tables |
fallbackBuilder |
Dynamic context-aware builder receiving missing roles & permissions | Custom upgrade banners |
π‘οΈ Custom Permission Checker Callback #
Connect external authentication engines or custom claim evaluators:
GuardifyScope(
permissionChecker: (allowedRoles, {requireAll = false, activeRoles, allowedPermissions, activePermissions}) {
return myAuthService.evaluate(
allowedRoles: allowedRoles,
allowedPermissions: allowedPermissions,
);
},
child: const MyApp(),
)
πΊοΈ Roadmap #
Check out our detailed ROADMAP.md to explore upcoming features including compliance CLI tools and automated test scopes.
π License #
This project is licensed under the MIT License - see the LICENSE file for details.