Skip to content

Permissions & Security

Air Framework provides a robust security layer designed to manage interactions between modules, especially useful in enterprise environments.

The framework uses a declarative permission system. Each module can declare what actions it is allowed to perform.

const authPermissions = ModulePermissions([
ScopedPermission(Permission.dataRead),
ScopedPermission(Permission.dataWrite, 'user.*'),
ScopedPermission(Permission.serviceCall, 'auth.*'),
]);
PermissionChecker().registerModule('auth', authPermissions);
  • Debug Mode (Default): Permission violations only log a Yellow Warning to the console but allow the action to proceed. This ensures fast development.
  • Strict Mode: Violations throw a SecurityException. Enable this in production:
    PermissionChecker().enable(); // Enable strict enforcement

Instead of registering services directly in the DI, you can use the SecureServiceRegistry to restrict who can call your services.

SecureServiceRegistry().registerService(
name: 'payments.process',
ownerModuleId: 'payments',
service: (amount) => _process(amount),
allowedCallers: ['checkout'], // Only 'checkout' module can call this
);

You can store shared data that automatically expires after a certain time.

SecureServiceRegistry().setSecureData<String>(
'auth.token',
'jwt-content',
callerModuleId: 'auth',
ttl: Duration(hours: 2),
);

All available permission types in air_framework:

PermissionDescription
Permission.dataReadRead any module’s shared data
Permission.dataWriteWrite to another module’s data scope
Permission.serviceCallCall a service registered by another module
Permission.eventEmitEmit events via the EventBus
Permission.eventSubscribeSubscribe to another module’s events
Permission.uiNavigateTrigger navigation to routes owned by another module
Permission.configReadRead another module’s configuration
Permission.configWriteModify another module’s configuration
sequenceDiagram
    participant Caller as Caller Module
    participant Checker as PermissionChecker
    participant Registry as ModulePermissions

    Caller->>Checker: check(callerModule, Permission.serviceCall, 'payments.*')
    Checker->>Registry: lookup permissions for Caller
    Registry-->>Checker: ScopedPermission list
    alt Permission granted
        Checker-->>Caller: ✅ Allowed
    else Debug mode — violation
        Checker-->>Caller: ⚠️ Warning logged, allowed
    else Strict mode — violation
        Checker-->>Caller: ❌ SecurityException thrown
    end
  • Enable strict mode in production: Call PermissionChecker().enable() in release builds to transform warnings into hard errors.
  • Declare minimal permissions: Only request the specific scopes your module needs — avoid broad wildcards like '*'.
  • Use TTL for sensitive data: Always set a ttl on setSecureData for tokens, session keys, and PII. A TTL of Duration.zero means the data never expires — use this only for non-sensitive static config.
  • Prefer the EventBus over direct service calls: Cross-module communication via events is decoupled and auditable. Direct serviceCall permissions tighten coupling.
  • Audit the Permissions Inspector: During development, review the Permissions tab in Flutter DevTools to catch unexpected cross-module access before it reaches production.