Security Features with fasqhooks
fasq_hooks supports all FASQ security features through QueryClient configuration, enabling secure data handling in your hooks-based applications.
Overview
Section titled “Overview”Security features in fasq_hooks include:
- Secure cache entries with automatic cleanup
- Encrypted persistence for sensitive data
- Input validation preventing injection attacks
- Platform-specific secure key storage
Secure Queries with useQuery
Section titled “Secure Queries with useQuery”Mark sensitive data to prevent persistence and enable automatic cleanup:
class SecureDataWidget extends HookWidget { @override Widget build(BuildContext context) { final client = context.queryClient;
final secureQuery = useQuery<String>( 'auth-token', () => api.getAuthToken(), options: QueryOptions( isSecure: true, // Mark as secure maxAge: Duration(minutes: 15), // Required TTL staleTime: Duration(minutes: 5), ), client: client, // Use configured client );
if (secureQuery.isLoading) return CircularProgressIndicator(); if (secureQuery.hasError) return Text('Error: ${secureQuery.error}');
// Secure data never persisted, cleared on app background return Text('Token: ${secureQuery.data}'); }}Security Benefits
Section titled “Security Benefits”- Never persisted to disk - Secure entries are memory-only
- Automatic cleanup - Cleared on app background/termination
- Strict TTL enforcement - Expired secure entries are immediately removed
- Not exposed in DevTools - Secure data is hidden from debugging tools
Secure Mutations with useMutation
Section titled “Secure Mutations with useMutation”Handle sensitive mutations with security features:
class SecureMutationWidget extends HookWidget { @override Widget build(BuildContext context) { final client = context.queryClient;
final mutation = useMutation<String, String>( mutationFn: (data) => api.secureMutation(data), options: MutationOptions( queueWhenOffline: true, maxRetries: 3, ), client: client, // Use configured client );
return ElevatedButton( onPressed: mutation.isLoading ? null : () => mutation.mutate('secure-data'), child: mutation.isLoading ? CircularProgressIndicator() : Text('Secure Mutation'), ); }}Global Security Configuration
Section titled “Global Security Configuration”Configure security features globally using QueryClientProvider:
final secureClient = QueryClient( config: const CacheConfig( defaultStaleTime: Duration(minutes: 5), defaultCacheTime: Duration(minutes: 10), ), persistenceOptions: const PersistenceOptions(enabled: true),);
QueryClientProvider( client: secureClient, child: const MaterialApp( home: MyApp(), ),);Accessing Configured Client
Section titled “Accessing Configured Client”Use the configured QueryClient in your hooks:
class MyWidget extends HookWidget { @override Widget build(BuildContext context) { final client = context.queryClient;
// Use configured client in hooks final secureQuery = useQuery<String>( 'secure-data', () => fetchSecureData(), options: QueryOptions( isSecure: true, maxAge: Duration(minutes: 30), ), client: client, );
final mutation = useMutation<String, String>( mutationFn: (data) => secureMutation(data), client: client, );
return Column( children: [ Text('Data: ${secureQuery.data}'), ElevatedButton( onPressed: () => mutation.mutate('data'), child: Text('Mutate'), ), ], ); }}Complete Security Example
Section titled “Complete Security Example”Here’s a complete example showing security features in a hooks-based app:
class SecureApp extends StatelessWidget { @override Widget build(BuildContext context) { final secureClient = QueryClient( config: const CacheConfig( defaultStaleTime: Duration(minutes: 5), defaultCacheTime: Duration(minutes: 10), ), persistenceOptions: const PersistenceOptions(enabled: true), );
return QueryClientProvider( client: secureClient, child: const MaterialApp( home: SecureHomeScreen(), ), ); }}
class SecureHomeScreen extends HookWidget { @override Widget build(BuildContext context) { final client = context.queryClient;
// Secure authentication token final authToken = useQuery<String>( 'auth-token', () => api.getAuthToken(), options: QueryOptions( isSecure: true, maxAge: Duration(minutes: 15), staleTime: Duration(minutes: 5), ), client: client, );
// Secure user profile final userProfile = useQuery<User>( 'user-profile', () => api.getUserProfile(), options: QueryOptions( isSecure: true, maxAge: Duration(minutes: 30), staleTime: Duration(minutes: 10), ), client: client, );
// Secure mutation for updating profile final updateProfile = useMutation<User, User>( mutationFn: (user) => api.updateUserProfile(user), options: MutationOptions( queueWhenOffline: true, maxRetries: 3, onSuccess: (user) { // Invalidate user profile query client.invalidateQuery('user-profile'); }, ), client: client, );
return Scaffold( appBar: AppBar(title: Text('Secure App')), body: Column( children: [ // Display auth token if (authToken.isLoading) CircularProgressIndicator() else if (authToken.hasError) Text('Error: ${authToken.error}') else Text('Token: ${authToken.data}'),
SizedBox(height: 20),
// Display user profile if (userProfile.isLoading) CircularProgressIndicator() else if (userProfile.hasError) Text('Error: ${userProfile.error}') else Text('User: ${userProfile.data?.name}'),
SizedBox(height: 20),
// Update profile button ElevatedButton( onPressed: updateProfile.isLoading ? null : () => updateProfile.mutate( User(name: 'Updated Name'), ), child: updateProfile.isLoading ? CircularProgressIndicator() : Text('Update Profile'), ), ], ), ); }}Custom Security Hooks
Section titled “Custom Security Hooks”Create custom hooks for common security patterns:
// Custom hook for secure authenticationQueryState<String> useSecureAuth() { final client = useQueryClient();
return useQuery<String>( 'auth-token', () => api.getAuthToken(), options: QueryOptions( isSecure: true, maxAge: Duration(minutes: 15), staleTime: Duration(minutes: 5), ), client: client, );}
// Custom hook for secure user profileQueryState<User> useSecureUserProfile() { final client = useQueryClient();
return useQuery<User>( 'user-profile', () => api.getUserProfile(), options: QueryOptions( isSecure: true, maxAge: Duration(minutes: 30), staleTime: Duration(minutes: 10), ), client: client, );}
// Custom hook for secure profile updateMutationState<User> useSecureProfileUpdate() { final client = useQueryClient();
return useMutation<User, User>( mutationFn: (user) => api.updateUserProfile(user), options: MutationOptions( queueWhenOffline: true, maxRetries: 3, onSuccess: (user) { client.invalidateQuery('user-profile'); }, ), client: client, );}
// Usage in widgetsclass MyWidget extends HookWidget { @override Widget build(BuildContext context) { final auth = useSecureAuth(); final profile = useSecureUserProfile(); final updateProfile = useSecureProfileUpdate();
return Column( children: [ Text('Token: ${auth.data}'), Text('User: ${profile.data?.name}'), ElevatedButton( onPressed: () => updateProfile.mutate(User(name: 'New Name')), child: Text('Update'), ), ], ); }}Security Best Practices
Section titled “Security Best Practices”1. Always Use Configured Client
Section titled “1. Always Use Configured Client”// Good - Use configured clientfinal client = useQueryClient();final query = useQuery<String>( 'secure-data', () => fetchData(), client: client,)
// Bad - Using default client without security configfinal query = useQuery<String>( 'secure-data', () => fetchData(), // Missing client parameter)2. Mark Sensitive Data as Secure
Section titled “2. Mark Sensitive Data as Secure”// Good - Sensitive data marked as secureQueryOptions( isSecure: true, maxAge: Duration(minutes: 15),)
// Bad - Sensitive data not marked as secureQueryOptions( isSecure: false, // Sensitive data could be persisted)3. Use Appropriate TTL Values
Section titled “3. Use Appropriate TTL Values”// Good - Short TTL for sensitive dataQueryOptions( isSecure: true, maxAge: Duration(minutes: 15), // Short-lived tokens)
// Bad - Too long TTL for sensitive dataQueryOptions( isSecure: true, maxAge: Duration(hours: 24), // Too long for sensitive data)4. Handle Security Errors Gracefully
Section titled “4. Handle Security Errors Gracefully”class SecureWidget extends HookWidget { @override Widget build(BuildContext context) { final client = useQueryClient();
final query = useQuery<String>( 'secure-data', () => fetchData(), client: client, );
// Handle security-related errors useEffect(() { if (query.hasError) { if (query.error.toString().contains('validation')) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('Invalid input detected')), ); } } return null; }, [query.hasError]);
return Text('${query.data}'); }}Migration Guide
Section titled “Migration Guide”Enabling Security in Existing Apps
Section titled “Enabling Security in Existing Apps”- Add the security package using the Flutter CLI:
flutter pub add fasq_security[!TIP] This package integrates with the Fasq core and the hooks adapter.
- Wrap your app with
QueryClientProvider:
QueryClientProvider( config: CacheConfig( defaultStaleTime: Duration(minutes: 5), defaultCacheTime: Duration(minutes: 10), ), persistenceOptions: PersistenceOptions( enabled: true, encryptionKey: 'your-encryption-key', ), child: MaterialApp( home: MyApp(), ),)- Update existing useQuery calls:
// Beforefinal query = useQuery<String>( 'auth-token', () => api.getAuthToken(),)
// Afterfinal client = useQueryClient();final query = useQuery<String>( 'auth-token', () => api.getAuthToken(), options: QueryOptions( isSecure: true, maxAge: Duration(minutes: 15), ), client: client,)- Update existing useMutation calls:
// Beforefinal mutation = useMutation<String, String>( mutationFn: (data) => api.mutate(data),)
// Afterfinal client = useQueryClient();final mutation = useMutation<String, String>( mutationFn: (data) => api.mutate(data), client: client,)Troubleshooting
Section titled “Troubleshooting”Common Issues
Section titled “Common Issues”Client not found:
- Ensure
QueryClientProviderwraps your app - Use
useQueryClient()hook to access the configured client
Security validation errors:
- Check query key format (alphanumeric, colon, hyphen, underscore only)
- Ensure durations are non-negative
- Verify cache data doesn’t contain functions
Performance issues:
- Large data (>50KB) is automatically encrypted in isolates
- Consider reducing cache size for better performance
- Use appropriate TTL values to prevent memory bloat
Error Messages
Section titled “Error Messages”| Error | Cause | Solution |
|---|---|---|
| ”Query key must contain only alphanumeric, colon, hyphen, underscore” | Invalid query key format | Use valid characters only |
| ”Secure queries must specify maxAge for TTL enforcement” | Missing maxAge for secure query | Add maxAge to QueryOptions |
| ”staleTime must be non-negative” | Negative duration | Use positive or zero duration |
| ”Cache data cannot be a function or closure” | Function in cache data | Remove functions from cached data |