Secure QR Validator
A robust Flutter package for validating secure QR codes with battle-tested encryption, digital signatures, and customizable business rules. Perfect for access control, ticketing, and secure document verification.
🔐 Compatible with Secure QR Generator package
Table of Contents
- Features
- Installation
- Quick Start
- Advanced Usage
- Business Rules
- Status Visualization
- Best Practices
- Error Handling
- License
Features
🔒 Secure Validation
- AES Encryption: Industry-standard AES-256 encryption support
- Digital Signatures: HMAC-SHA256 signature verification
- Temporal Validation: Time-based validation with expiration
- Data Access for Expired QR Codes: Access to data even when QR code is expired
- Replay Protection: Built-in mechanisms against replay attacks
- Tampering Detection: Automatic detection of modified QR codes
🎯 Business Rules
- Flexible Rules Engine: Define custom validation logic
- Pre-built Validations: Common validation rules ready to use
- Rule Composition: Combine rules using builder pattern
- Type-Safe: Generic type support for data validation
- Extensible: Easy to add new validation rules
🎨 Flutter Integration
- Ready-to-Use Widgets: Drop-in validation status displays
- Customizable UI: Full control over styling and animations
- Responsive: Adapts to different screen sizes
- Platform Support: Works on iOS, Android, and Web
- Dark Mode: Automatic theme adaptation
Installation
- Add dependency to your
pubspec.yaml
:
dependencies:
secure_qr_validator: ^1.1.0
- Install packages:
flutter pub get
- Import the package:
import 'package:secure_qr_validator/secure_qr_validator.dart';
Quick Start
Basic Validation
// Initialize validator
final validator = SecureQRValidator(
ValidatorConfig(
secretKey: 'your-secure-key-min-32-chars-long!!!',
validityDuration: Duration(minutes: 5),
enableEncryption: true,
enableSignature: true,
enableExpirationCheck: true, // Can be set to false to ignore expiration
),
);
// Perform validation
try {
final result = await validator.validateQRPayload(qrCodeContent);
if (result.isValid) {
// Access validated data safely
final userId = result.getData<String>('userId');
final accessLevel = result.getData<int>('accessLevel');
// Continue with business logic
handleValidAccess(userId, accessLevel);
} else if (result.isExpired && result.data != null) {
// Handle expired QR codes with data available
final userId = result.getData<String>('userId');
handleExpiredQRCode(userId, result.generatedAt);
} else {
handleInvalidAccess(result.error);
}
} catch (e) {
handleError(e);
}
With Business Rules
final validator = SecureQRValidator(
ValidatorConfig(
secretKey: 'your-secure-key-min-32-chars-long!!!',
validityDuration: Duration(minutes: 5),
enableExpirationCheck: true,
),
businessRules: [
// Required fields
CommonValidationRules.required(['userId', 'accessLevel']),
// Access level range check
CommonValidationRules.numberInRange('accessLevel', 1, 5),
// Custom business logic
(data) {
final accessLevel = data['accessLevel'] as int;
final role = data['role'] as String?;
if (role == 'admin' && accessLevel < 3) {
return 'Admin users must have access level 3 or higher';
}
return null; // Validation passed
},
],
);
Advanced Usage
Type-Safe Data Access
final result = await validator.validateQRPayload(qrContent);
// Data access works even for expired QR codes
if (!result.isValid && result.isExpired && result.data != null) {
// Process expired QR code data
final userId = result.getData<String>('userId');
showExpiredQrCodeMessage(userId);
}
// Safe data access with type checking
final UserProfile profile = result.getDataModel<UserProfile>(
'profile',
(json) => UserProfile.fromJson(json),
);
// With default values
final age = result.getData<int>('age', defaultValue: 0);
final name = result.getData<String>('name', defaultValue: 'Unknown');
// Presence checking
if (result.hasAllData(['email', 'phone'])) {
final contacts = Contacts(
email: result.getData<String>('email'),
phone: result.getData<String>('phone'),
);
}
Handling Expired QR Codes
// Check if a QR code is expired but still has accessible data
if (result.isExpired && result.data != null) {
// Access expiration details
final expirationDate = result.generatedAt?.add(
validator.config.validityDuration,
);
// Access the data even though it's expired
final ticketId = result.getData<String>('ticketId');
final userName = result.getData<String>('userName');
showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text('Expired Ticket'),
content: Text(
'This ticket expired on ${expirationDate?.toString()}.\n'
'Ticket ID: $ticketId\n'
'User: $userName'
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: Text('Close'),
),
TextButton(
onPressed: () => regenerateTicket(ticketId),
child: Text('Regenerate Ticket'),
),
],
),
);
}
Custom Validation Rules
class DateRangeRule implements ValidationRule {
final String startField;
final String endField;
DateRangeRule(this.startField, this.endField);
@override
String? validate(Map<String, dynamic> data) {
final start = DateTime.parse(data[startField] as String);
final end = DateTime.parse(data[endField] as String);
if (end.isBefore(start)) {
return 'End date must be after start date';
}
return null;
}
}
// Usage
validator.addRule(DateRangeRule('startDate', 'endDate'));
UI Integration
Status Indicator
ValidityIndicatorView(
result: validationResult,
builder: (context, status, child) => Container(
padding: EdgeInsets.all(16),
decoration: BoxDecoration(
color: status.color,
borderRadius: BorderRadius.circular(8),
),
child: Row(
children: [
Icon(status.icon),
SizedBox(width: 8),
Text(status.message),
],
),
),
)
Error Handling
try {
final result = await validator.validateQRPayload(qrContent);
if (result.isValid) {
handleValidationResult(result);
} else if (result.isExpired && result.data != null) {
// Special handling for expired QR codes with data
handleExpiredQRWithData(result);
} else {
handleInvalidQR(result.error);
}
} on ValidationException catch (e) {
// Handle validation errors (invalid format, expired, etc.)
showError('Validation Error', e.message);
} on SecurityException catch (e) {
// Handle security errors (invalid signature, decryption failed, etc.)
showError('Security Error', e.message);
} on BusinessRuleException catch (e) {
// Handle business rule violations
showError('Business Rule Violation', e.message);
} catch (e) {
// Handle unexpected errors
reportError(e);
}
Best Practices
Security
- Use strong encryption keys (32+ characters)
- Enable both encryption and signature verification
- Use short validity durations for sensitive operations
- Implement proper key management and rotation
- Add rate limiting for validation attempts
Performance
- Use appropriate validity durations
- Implement proper error handling
- Profile memory usage in production
Integration
- Use dependency injection for validator instances
- Add logging for security events
- Use type-safe data access
- Write comprehensive tests
Handling Expired QR Codes
- Decide whether to enable expiration checks based on your use case
- Implement graceful degradation for expired QR codes
- Consider adding refresh/regeneration flows for expired codes
- Add clear user messaging for expiration scenarios
License
This project is licensed under the MIT License - see the LICENSE file for details.