Authentication and Security
JWT Implementation
JSON Web Tokens (JWT) provide a stateless authentication mechanism that works seamlessly across all Hypermodern transport protocols. The platform includes built-in JWT support with automatic token validation and refresh capabilities.
JWT Service Implementation
import 'package:dart_jsonwebtoken/dart_jsonwebtoken.dart';
class JWTService {
final String _secretKey;
final String _issuer;
final Duration _accessTokenExpiry;
final Duration _refreshTokenExpiry;
JWTService({
required String secretKey,
required String issuer,
this._accessTokenExpiry = const Duration(hours: 1),
this._refreshTokenExpiry = const Duration(days: 7),
}) : _secretKey = secretKey, _issuer = issuer;
String generateAccessToken(User user, {Duration? customExpiry}) {
final expiry = customExpiry ?? _accessTokenExpiry;
final now = DateTime.now();
final jwt = JWT({
'iss': _issuer,
'sub': user.id.toString(),
'aud': 'hypermodern-api',
'exp': now.add(expiry).millisecondsSinceEpoch ~/ 1000,
'iat': now.millisecondsSinceEpoch ~/ 1000,
'nbf': now.millisecondsSinceEpoch ~/ 1000,
'jti': _generateJTI(),
'user_id': user.id,
'username': user.username,
'email': user.email,
'roles': user.roles.map((r) => r.name).toList(),
'permissions': _extractPermissions(user.roles),
'token_type': 'access',
});
return jwt.sign(SecretKey(_secretKey), algorithm: JWTAlgorithm.HS256);
}
String generateRefreshToken(User user) {
final now = DateTime.now();
final jwt = JWT({
'iss': _issuer,
'sub': user.id.toString(),
'aud': 'hypermodern-api',
'exp': now.add(_refreshTokenExpiry).millisecondsSinceEpoch ~/ 1000,
'iat': now.millisecondsSinceEpoch ~/ 1000,
'jti': _generateJTI(),
'user_id': user.id,
'token_type': 'refresh',
});
return jwt.sign(SecretKey(_secretKey), algorithm: JWTAlgorithm.HS256);
}
TokenValidationResult validateToken(String token) {
try {
final jwt = JWT.verify(token, SecretKey(_secretKey));
final payload = jwt.payload as Map<String, dynamic>;
// Check token type
final tokenType = payload['token_type'] as String?;
if (tokenType == null) {
return TokenValidationResult.invalid('Missing token type');
}
// Check expiration
final exp = payload['exp'] as int;
final now = DateTime.now().millisecondsSinceEpoch ~/ 1000;
if (now >= exp) {
return TokenValidationResult.expired();
}
// Check not before
final nbf = payload['nbf'] as int?;
if (nbf != null && now < nbf) {
return TokenValidationResult.invalid('Token not yet valid');
}
return TokenValidationResult.valid(TokenClaims.fromPayload(payload));
} on JWTExpiredException {
return TokenValidationResult.expired();
} on JWTException catch (e) {
return TokenValidationResult.invalid(e.message);
} catch (e) {
return TokenValidationResult.invalid('Token validation failed: $e');
}
}
Future<void> revokeToken(String token) async {
// Add token to blacklist
final jwt = JWT.verify(token, SecretKey(_secretKey));
final jti = jwt.payload['jti'] as String;
await _addToBlacklist(jti);
}
Future<bool> isTokenBlacklisted(String jti) async {
// Check if token is in blacklist
return await _isInBlacklist(jti);
}
List<String> _extractPermissions(List<Role> roles) {
final permissions = <String>{};
for (final role in roles) {
permissions.addAll(role.permissions.map((p) => p.name));
}
return permissions.toList();
}
String _generateJTI() {
return '${DateTime.now().millisecondsSinceEpoch}_${_generateRandomString(8)}';
}
String _generateRandomString(int length) {
const chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
final random = Random.secure();
return String.fromCharCodes(
Iterable.generate(length, (_) => chars.codeUnitAt(random.nextInt(chars.length))),
);
}
Future<void> _addToBlacklist(String jti) async {
// Implementation depends on your storage choice (Redis, Database, etc.)
// This is a simplified example
await tokenBlacklistService.add(jti);
}
Future<bool> _isInBlacklist(String jti) async {
return await tokenBlacklistService.contains(jti);
}
}
class TokenClaims {
final int userId;
final String username;
final String email;
final List<String> roles;
final List<String> permissions;
final String tokenType;
final DateTime issuedAt;
final DateTime expiresAt;
final String jti;
TokenClaims({
required this.userId,
required this.username,
required this.email,
required this.roles,
required this.permissions,
required this.tokenType,
required this.issuedAt,
required this.expiresAt,
required this.jti,
});
factory TokenClaims.fromPayload(Map<String, dynamic> payload) {
return TokenClaims(
userId: payload['user_id'] as int,
username: payload['username'] as String,
email: payload['email'] as String,
roles: (payload['roles'] as List).cast<String>(),
permissions: (payload['permissions'] as List).cast<String>(),
tokenType: payload['token_type'] as String,
issuedAt: DateTime.fromMillisecondsSinceEpoch((payload['iat'] as int) * 1000),
expiresAt: DateTime.fromMillisecondsSinceEpoch((payload['exp'] as int) * 1000),
jti: payload['jti'] as String,
);
}
bool hasPermission(String permission) {
return permissions.contains(permission);
}
bool hasRole(String role) {
return roles.contains(role);
}
bool hasAnyRole(List<String> requiredRoles) {
return requiredRoles.any((role) => roles.contains(role));
}
bool hasAllRoles(List<String> requiredRoles) {
return requiredRoles.every((role) => roles.contains(role));
}
}
class TokenValidationResult {
final bool isValid;
final bool isExpired;
final String? error;
final TokenClaims? claims;
TokenValidationResult._({
required this.isValid,
required this.isExpired,
this.error,
this.claims,
});
factory TokenValidationResult.valid(TokenClaims claims) {
return TokenValidationResult._(
isValid: true,
isExpired: false,
claims: claims,
);
}
factory TokenValidationResult.expired() {
return TokenValidationResult._(
isValid: false,
isExpired: true,
error: 'Token has expired',
);
}
factory TokenValidationResult.invalid(String error) {
return TokenValidationResult._(
isValid: false,
isExpired: false,
error: error,
);
}
}
Token Blacklist Service
abstract class TokenBlacklistService {
Future<void> add(String jti, {Duration? ttl});
Future<bool> contains(String jti);
Future<void> remove(String jti);
Future<void> cleanup();
}
class RedisTokenBlacklistService implements TokenBlacklistService {
final RedisConnection _redis;
final String _keyPrefix;
RedisTokenBlacklistService(this._redis, {String keyPrefix = 'blacklist:token:'})
: _keyPrefix = keyPrefix;
@override
Future<void> add(String jti, {Duration? ttl}) async {
final key = '$_keyPrefix$jti';
await _redis.set(key, '1');
if (ttl != null) {
await _redis.expire(key, ttl.inSeconds);
}
}
@override
Future<bool> contains(String jti) async {
final key = '$_keyPrefix$jti';
final result = await _redis.get(key);
return result != null;
}
@override
Future<void> remove(String jti) async {
final key = '$_keyPrefix$jti';
await _redis.del([key]);
}
@override
Future<void> cleanup() async {
// Redis handles TTL automatically, but we can implement
// additional cleanup logic if needed
}
}
class DatabaseTokenBlacklistService implements TokenBlacklistService {
final Database _db;
DatabaseTokenBlacklistService(this._db);
@override
Future<void> add(String jti, {Duration? ttl}) async {
final expiresAt = ttl != null ? DateTime.now().add(ttl) : null;
await _db.query(
'INSERT INTO token_blacklist (jti, expires_at, created_at) VALUES (?, ?, ?)',
[jti, expiresAt, DateTime.now()],
);
}
@override
Future<bool> contains(String jti) async {
final result = await _db.query(
'''
SELECT 1 FROM token_blacklist
WHERE jti = ? AND (expires_at IS NULL OR expires_at > ?)
''',
[jti, DateTime.now()],
);
return result.isNotEmpty;
}
@override
Future<void> remove(String jti) async {
await _db.query('DELETE FROM token_blacklist WHERE jti = ?', [jti]);
}
@override
Future<void> cleanup() async {
await _db.query(
'DELETE FROM token_blacklist WHERE expires_at IS NOT NULL AND expires_at <= ?',
[DateTime.now()],
);
}
}
Middleware-based Authentication
Authentication Middleware
class AuthenticationMiddleware implements Middleware {
final JWTService _jwtService;
final TokenBlacklistService _blacklistService;
final Set<String> _publicEndpoints;
final Set<String> _optionalAuthEndpoints;
AuthenticationMiddleware({
required JWTService jwtService,
required TokenBlacklistService blacklistService,
Set<String> publicEndpoints = const {},
Set<String> optionalAuthEndpoints = const {},
}) : _jwtService = jwtService,
_blacklistService = blacklistService,
_publicEndpoints = publicEndpoints,
_optionalAuthEndpoints = optionalAuthEndpoints;
@override
Future<dynamic> handle(
dynamic request,
Future<dynamic> Function(dynamic) next,
) async {
final endpoint = request.endpoint as String;
// Skip authentication for public endpoints
if (_publicEndpoints.contains(endpoint)) {
return await next(request);
}
// Extract token from request
final token = _extractToken(request);
// Handle optional authentication
if (_optionalAuthEndpoints.contains(endpoint)) {
if (token != null) {
await _validateAndSetUser(request, token);
}
return await next(request);
}
// Require authentication for protected endpoints
if (token == null) {
throw UnauthorizedException('Authentication required');
}
await _validateAndSetUser(request, token);
return await next(request);
}
String? _extractToken(dynamic request) {
// Try different token sources based on protocol
switch (request.protocol) {
case 'http':
return _extractHttpToken(request);
case 'websocket':
return _extractWebSocketToken(request);
case 'tcp':
return _extractTcpToken(request);
default:
return null;
}
}
String? _extractHttpToken(HttpRequest request) {
// Check Authorization header
final authHeader = request.headers['authorization'];
if (authHeader != null && authHeader.startsWith('Bearer ')) {
return authHeader.substring(7);
}
// Check query parameter (less secure, use with caution)
return request.uri.queryParameters['token'];
}
String? _extractWebSocketToken(WebSocketRequest request) {
// Check query parameter
final token = request.uri.queryParameters['token'];
if (token != null) return token;
// Check headers if available
final authHeader = request.headers['authorization'];
if (authHeader != null && authHeader.startsWith('Bearer ')) {
return authHeader.substring(7);
}
return null;
}
String? _extractTcpToken(TcpRequest request) {
// For TCP, token might be in the request metadata
return request.metadata['auth_token'] as String?;
}
Future<void> _validateAndSetUser(dynamic request, String token) async {
// Validate token format and signature
final validationResult = _jwtService.validateToken(token);
if (!validationResult.isValid) {
if (validationResult.isExpired) {
throw TokenExpiredException('Token has expired');
} else {
throw UnauthorizedException('Invalid token: ${validationResult.error}');
}
}
final claims = validationResult.claims!;
// Check if token is blacklisted
if (await _blacklistService.contains(claims.jti)) {
throw UnauthorizedException('Token has been revoked');
}
// Set user context in request
request.context['user_id'] = claims.userId;
request.context['username'] = claims.username;
request.context['email'] = claims.email;
request.context['roles'] = claims.roles;
request.context['permissions'] = claims.permissions;
request.context['token_claims'] = claims;
request.context['authenticated'] = true;
}
}
Authorization Middleware
class AuthorizationMiddleware implements Middleware {
final Map<String, List<String>> _endpointPermissions;
final Map<String, List<String>> _endpointRoles;
AuthorizationMiddleware({
Map<String, List<String>> endpointPermissions = const {},
Map<String, List<String>> endpointRoles = const {},
}) : _endpointPermissions = endpointPermissions,
_endpointRoles = endpointRoles;
@override
Future<dynamic> handle(
dynamic request,
Future<dynamic> Function(dynamic) next,
) async {
final endpoint = request.endpoint as String;
final isAuthenticated = request.context['authenticated'] as bool? ?? false;
// Skip authorization if not authenticated (handled by AuthenticationMiddleware)
if (!isAuthenticated) {
return await next(request);
}
final userPermissions = (request.context['permissions'] as List<String>?) ?? [];
final userRoles = (request.context['roles'] as List<String>?) ?? [];
// Check required permissions
final requiredPermissions = _endpointPermissions[endpoint];
if (requiredPermissions != null && requiredPermissions.isNotEmpty) {
final hasPermission = requiredPermissions.any((perm) => userPermissions.contains(perm));
if (!hasPermission) {
throw ForbiddenException(
'Insufficient permissions. Required: ${requiredPermissions.join(', ')}',
);
}
}
// Check required roles
final requiredRoles = _endpointRoles[endpoint];
if (requiredRoles != null && requiredRoles.isNotEmpty) {
final hasRole = requiredRoles.any((role) => userRoles.contains(role));
if (!hasRole) {
throw ForbiddenException(
'Insufficient roles. Required: ${requiredRoles.join(', ')}',
);
}
}
return await next(request);
}
}
Role-Based Access Control (RBAC)
class RBACService {
final Database _db;
final Map<String, Role> _roleCache = {};
final Map<String, Permission> _permissionCache = {};
RBACService(this._db);
Future<List<Role>> getUserRoles(int userId) async {
final result = await _db.query('''
SELECT r.* FROM roles r
JOIN user_roles ur ON r.id = ur.role_id
WHERE ur.user_id = ? AND ur.revoked_at IS NULL
''', [userId]);
return result.map((row) => Role.fromJson(row)).toList();
}
Future<List<Permission>> getRolePermissions(int roleId) async {
final result = await _db.query('''
SELECT p.* FROM permissions p
JOIN role_permissions rp ON p.id = rp.permission_id
WHERE rp.role_id = ?
''', [roleId]);
return result.map((row) => Permission.fromJson(row)).toList();
}
Future<List<Permission>> getUserPermissions(int userId) async {
final result = await _db.query('''
SELECT DISTINCT p.* FROM permissions p
JOIN role_permissions rp ON p.id = rp.permission_id
JOIN user_roles ur ON rp.role_id = ur.role_id
WHERE ur.user_id = ? AND ur.revoked_at IS NULL
''', [userId]);
return result.map((row) => Permission.fromJson(row)).toList();
}
Future<bool> userHasPermission(int userId, String permission) async {
final result = await _db.query('''
SELECT 1 FROM permissions p
JOIN role_permissions rp ON p.id = rp.permission_id
JOIN user_roles ur ON rp.role_id = ur.role_id
WHERE ur.user_id = ? AND p.name = ? AND ur.revoked_at IS NULL
LIMIT 1
''', [userId, permission]);
return result.isNotEmpty;
}
Future<bool> userHasRole(int userId, String roleName) async {
final result = await _db.query('''
SELECT 1 FROM roles r
JOIN user_roles ur ON r.id = ur.role_id
WHERE ur.user_id = ? AND r.name = ? AND ur.revoked_at IS NULL
LIMIT 1
''', [userId, roleName]);
return result.isNotEmpty;
}
Future<void> assignRole(int userId, int roleId, {int? assignedBy}) async {
await _db.query('''
INSERT INTO user_roles (user_id, role_id, assigned_at, assigned_by)
VALUES (?, ?, ?, ?)
ON CONFLICT (user_id, role_id)
DO UPDATE SET revoked_at = NULL, assigned_at = ?, assigned_by = ?
''', [userId, roleId, DateTime.now(), assignedBy, DateTime.now(), assignedBy]);
}
Future<void> revokeRole(int userId, int roleId, {int? revokedBy}) async {
await _db.query('''
UPDATE user_roles
SET revoked_at = ?, revoked_by = ?
WHERE user_id = ? AND role_id = ? AND revoked_at IS NULL
''', [DateTime.now(), revokedBy, userId, roleId]);
}
Future<Role> createRole({
required String name,
required String description,
required List<int> permissionIds,
}) async {
return await _db.transaction((tx) async {
// Create role
final roleResult = await tx.query('''
INSERT INTO roles (name, description, created_at)
VALUES (?, ?, ?)
RETURNING *
''', [name, description, DateTime.now()]);
final role = Role.fromJson(roleResult.first);
// Assign permissions
for (final permissionId in permissionIds) {
await tx.query('''
INSERT INTO role_permissions (role_id, permission_id)
VALUES (?, ?)
''', [role.id, permissionId]);
}
return role;
});
}
Future<Permission> createPermission({
required String name,
required String description,
required String resource,
required String action,
}) async {
final result = await _db.query('''
INSERT INTO permissions (name, description, resource, action, created_at)
VALUES (?, ?, ?, ?, ?)
RETURNING *
''', [name, description, resource, action, DateTime.now()]);
return Permission.fromJson(result.first);
}
}
Security Best Practices
Input Validation and Sanitization
class SecurityValidator {
static final RegExp _emailRegex = RegExp(
r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$',
);
static final RegExp _usernameRegex = RegExp(r'^[a-zA-Z0-9_]{3,20}$');
static final RegExp _sqlInjectionPatterns = RegExp(
r'(\b(SELECT|INSERT|UPDATE|DELETE|DROP|CREATE|ALTER|EXEC|UNION|SCRIPT)\b)',
caseSensitive: false,
);
static final RegExp _xssPatterns = RegExp(
r'(<script|javascript:|on\w+\s*=|<iframe|<object|<embed)',
caseSensitive: false,
);
static void validateEmail(String email) {
if (!_emailRegex.hasMatch(email)) {
throw ValidationException('Invalid email format');
}
}
static void validateUsername(String username) {
if (!_usernameRegex.hasMatch(username)) {
throw ValidationException(
'Username must be 3-20 characters and contain only letters, numbers, and underscores',
);
}
}
static void validatePassword(String password, {int minLength = 8}) {
if (password.length < minLength) {
throw ValidationException('Password must be at least $minLength characters');
}
if (!password.contains(RegExp(r'[A-Z]'))) {
throw ValidationException('Password must contain at least one uppercase letter');
}
if (!password.contains(RegExp(r'[a-z]'))) {
throw ValidationException('Password must contain at least one lowercase letter');
}
if (!password.contains(RegExp(r'[0-9]'))) {
throw ValidationException('Password must contain at least one number');
}
if (!password.contains(RegExp(r'[!@#$%^&*(),.?":{}|<>]'))) {
throw ValidationException('Password must contain at least one special character');
}
}
static String sanitizeInput(String input) {
// Remove potential SQL injection patterns
String sanitized = input.replaceAll(_sqlInjectionPatterns, '');
// Remove potential XSS patterns
sanitized = sanitized.replaceAll(_xssPatterns, '');
// HTML encode special characters
sanitized = sanitized
.replaceAll('&', '&')
.replaceAll('<', '<')
.replaceAll('>', '>')
.replaceAll('"', '"')
.replaceAll("'", ''');
return sanitized.trim();
}
static void validateFileUpload(String filename, List<int> content) {
// Check file extension
final allowedExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.pdf', '.doc', '.docx'];
final extension = filename.toLowerCase().substring(filename.lastIndexOf('.'));
if (!allowedExtensions.contains(extension)) {
throw ValidationException('File type not allowed');
}
// Check file size (10MB limit)
if (content.length > 10 * 1024 * 1024) {
throw ValidationException('File size exceeds limit');
}
// Check for malicious content (basic check)
final contentString = String.fromCharCodes(content.take(1024));
if (contentString.contains('<script') || contentString.contains('javascript:')) {
throw ValidationException('File contains potentially malicious content');
}
}
}
Rate Limiting
class RateLimitMiddleware implements Middleware {
final RateLimiter _rateLimiter;
final Map<String, RateLimit> _endpointLimits;
final RateLimit _defaultLimit;
RateLimitMiddleware({
required RateLimiter rateLimiter,
Map<String, RateLimit> endpointLimits = const {},
RateLimit defaultLimit = const RateLimit(requests: 100, window: Duration(minutes: 1)),
}) : _rateLimiter = rateLimiter,
_endpointLimits = endpointLimits,
_defaultLimit = defaultLimit;
@override
Future<dynamic> handle(
dynamic request,
Future<dynamic> Function(dynamic) next,
) async {
final clientId = _getClientIdentifier(request);
final endpoint = request.endpoint as String;
final limit = _endpointLimits[endpoint] ?? _defaultLimit;
final allowed = await _rateLimiter.isAllowed(
clientId,
endpoint,
limit,
);
if (!allowed.isAllowed) {
throw RateLimitExceededException(
'Rate limit exceeded. Try again in ${allowed.retryAfter} seconds',
retryAfter: allowed.retryAfter,
);
}
final response = await next(request);
// Add rate limit headers for HTTP responses
if (request.protocol == 'http') {
response.headers.addAll({
'X-RateLimit-Limit': limit.requests.toString(),
'X-RateLimit-Remaining': allowed.remaining.toString(),
'X-RateLimit-Reset': allowed.resetTime.millisecondsSinceEpoch.toString(),
});
}
return response;
}
String _getClientIdentifier(dynamic request) {
// Try to get authenticated user ID first
final userId = request.context['user_id'] as int?;
if (userId != null) {
return 'user:$userId';
}
// Fall back to IP address
final clientIp = request.clientIp as String? ?? 'unknown';
return 'ip:$clientIp';
}
}
class RateLimiter {
final Map<String, ClientRateLimit> _clients = {};
final Duration _cleanupInterval;
Timer? _cleanupTimer;
RateLimiter({this._cleanupInterval = const Duration(minutes: 5)}) {
_startCleanup();
}
Future<RateLimitResult> isAllowed(
String clientId,
String endpoint,
RateLimit limit,
) async {
final key = '$clientId:$endpoint';
final now = DateTime.now();
final clientLimit = _clients.putIfAbsent(key, () => ClientRateLimit(
requests: 0,
windowStart: now,
limit: limit,
));
// Reset window if expired
if (now.difference(clientLimit.windowStart) >= limit.window) {
clientLimit.requests = 0;
clientLimit.windowStart = now;
}
// Check if limit exceeded
if (clientLimit.requests >= limit.requests) {
final resetTime = clientLimit.windowStart.add(limit.window);
final retryAfter = resetTime.difference(now).inSeconds;
return RateLimitResult(
isAllowed: false,
remaining: 0,
resetTime: resetTime,
retryAfter: retryAfter,
);
}
// Increment request count
clientLimit.requests++;
return RateLimitResult(
isAllowed: true,
remaining: limit.requests - clientLimit.requests,
resetTime: clientLimit.windowStart.add(limit.window),
retryAfter: 0,
);
}
void _startCleanup() {
_cleanupTimer = Timer.periodic(_cleanupInterval, (_) {
final now = DateTime.now();
_clients.removeWhere((key, clientLimit) {
return now.difference(clientLimit.windowStart) > clientLimit.limit.window;
});
});
}
void dispose() {
_cleanupTimer?.cancel();
}
}
class RateLimit {
final int requests;
final Duration window;
const RateLimit({required this.requests, required this.window});
}
class ClientRateLimit {
int requests;
DateTime windowStart;
final RateLimit limit;
ClientRateLimit({
required this.requests,
required this.windowStart,
required this.limit,
});
}
class RateLimitResult {
final bool isAllowed;
final int remaining;
final DateTime resetTime;
final int retryAfter;
RateLimitResult({
required this.isAllowed,
required this.remaining,
required this.resetTime,
required this.retryAfter,
});
}
Cross-Protocol Security
class SecurityHeadersMiddleware implements Middleware {
final Map<String, String> _securityHeaders;
SecurityHeadersMiddleware({
Map<String, String>? customHeaders,
}) : _securityHeaders = {
'X-Content-Type-Options': 'nosniff',
'X-Frame-Options': 'DENY',
'X-XSS-Protection': '1; mode=block',
'Strict-Transport-Security': 'max-age=31536000; includeSubDomains',
'Content-Security-Policy': "default-src 'self'",
'Referrer-Policy': 'strict-origin-when-cross-origin',
'Permissions-Policy': 'geolocation=(), microphone=(), camera=()',
...?customHeaders,
};
@override
Future<dynamic> handle(
dynamic request,
Future<dynamic> Function(dynamic) next,
) async {
final response = await next(request);
// Add security headers for HTTP responses
if (request.protocol == 'http') {
response.headers.addAll(_securityHeaders);
}
return response;
}
}
class CORSMiddleware implements Middleware {
final List<String> _allowedOrigins;
final List<String> _allowedMethods;
final List<String> _allowedHeaders;
final bool _allowCredentials;
final Duration _maxAge;
CORSMiddleware({
List<String> allowedOrigins = const ['*'],
List<String> allowedMethods = const ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
List<String> allowedHeaders = const ['Content-Type', 'Authorization'],
bool allowCredentials = false,
Duration maxAge = const Duration(hours: 24),
}) : _allowedOrigins = allowedOrigins,
_allowedMethods = allowedMethods,
_allowedHeaders = allowedHeaders,
_allowCredentials = allowCredentials,
_maxAge = maxAge;
@override
Future<dynamic> handle(
dynamic request,
Future<dynamic> Function(dynamic) next,
) async {
// Only apply CORS to HTTP requests
if (request.protocol != 'http') {
return await next(request);
}
final origin = request.headers['origin'];
// Handle preflight requests
if (request.method == 'OPTIONS') {
return _handlePreflightRequest(request, origin);
}
final response = await next(request);
// Add CORS headers to actual requests
if (origin != null && _isOriginAllowed(origin)) {
response.headers.addAll({
'Access-Control-Allow-Origin': origin,
'Access-Control-Allow-Methods': _allowedMethods.join(', '),
'Access-Control-Allow-Headers': _allowedHeaders.join(', '),
if (_allowCredentials) 'Access-Control-Allow-Credentials': 'true',
'Vary': 'Origin',
});
}
return response;
}
dynamic _handlePreflightRequest(dynamic request, String? origin) {
if (origin == null || !_isOriginAllowed(origin)) {
return HttpResponse(statusCode: 403, body: 'Origin not allowed');
}
return HttpResponse(
statusCode: 200,
headers: {
'Access-Control-Allow-Origin': origin,
'Access-Control-Allow-Methods': _allowedMethods.join(', '),
'Access-Control-Allow-Headers': _allowedHeaders.join(', '),
'Access-Control-Max-Age': _maxAge.inSeconds.toString(),
if (_allowCredentials) 'Access-Control-Allow-Credentials': 'true',
'Vary': 'Origin',
},
);
}
bool _isOriginAllowed(String origin) {
if (_allowedOrigins.contains('*')) {
return true;
}
return _allowedOrigins.any((allowed) {
if (allowed.startsWith('*.')) {
final domain = allowed.substring(2);
return origin.endsWith(domain);
}
return origin == allowed;
});
}
}
Encryption and Hashing
class CryptographyService {
final String _encryptionKey;
final String _hashSalt;
CryptographyService({
required String encryptionKey,
required String hashSalt,
}) : _encryptionKey = encryptionKey,
_hashSalt = hashSalt;
// Password hashing using bcrypt
Future<String> hashPassword(String password) async {
final salt = BCrypt.gensalt(rounds: 12);
return BCrypt.hashpw(password, salt);
}
Future<bool> verifyPassword(String password, String hash) async {
return BCrypt.checkpw(password, hash);
}
// Data encryption using AES
String encryptData(String data) {
final key = Key.fromBase64(_encryptionKey);
final iv = IV.fromSecureRandom(16);
final encrypter = Encrypter(AES(key));
final encrypted = encrypter.encrypt(data, iv: iv);
return '${iv.base64}:${encrypted.base64}';
}
String decryptData(String encryptedData) {
final parts = encryptedData.split(':');
if (parts.length != 2) {
throw ArgumentError('Invalid encrypted data format');
}
final key = Key.fromBase64(_encryptionKey);
final iv = IV.fromBase64(parts[0]);
final encrypter = Encrypter(AES(key));
final encrypted = Encrypted.fromBase64(parts[1]);
return encrypter.decrypt(encrypted, iv: iv);
}
// Secure random token generation
String generateSecureToken({int length = 32}) {
final random = Random.secure();
final bytes = List<int>.generate(length, (_) => random.nextInt(256));
return base64Url.encode(bytes);
}
// HMAC signing
String signData(String data) {
final key = utf8.encode(_hashSalt);
final bytes = utf8.encode(data);
final hmac = Hmac(sha256, key);
final digest = hmac.convert(bytes);
return digest.toString();
}
bool verifySignature(String data, String signature) {
final expectedSignature = signData(data);
return expectedSignature == signature;
}
// Time-based one-time password (TOTP)
String generateTOTP(String secret, {int timeStep = 30}) {
final time = DateTime.now().millisecondsSinceEpoch ~/ 1000;
final counter = time ~/ timeStep;
final key = base32.decode(secret);
final counterBytes = ByteData(8)..setUint64(0, counter, Endian.big);
final hmac = Hmac(sha1, key);
final hash = hmac.convert(counterBytes.buffer.asUint8List());
final offset = hash.bytes.last & 0x0f;
final binary = ByteData.sublistView(Uint8List.fromList(hash.bytes), offset, offset + 4)
.getUint32(0, Endian.big) & 0x7fffffff;
final otp = binary % 1000000;
return otp.toString().padLeft(6, '0');
}
bool verifyTOTP(String secret, String token, {int timeStep = 30, int window = 1}) {
final time = DateTime.now().millisecondsSinceEpoch ~/ 1000;
final currentCounter = time ~/ timeStep;
// Check current time and adjacent time windows
for (int i = -window; i <= window; i++) {
final counter = currentCounter + i;
final counterBytes = ByteData(8)..setUint64(0, counter, Endian.big);
final key = base32.decode(secret);
final hmac = Hmac(sha1, key);
final hash = hmac.convert(counterBytes.buffer.asUint8List());
final offset = hash.bytes.last & 0x0f;
final binary = ByteData.sublistView(Uint8List.fromList(hash.bytes), offset, offset + 4)
.getUint32(0, Endian.big) & 0x7fffffff;
final otp = binary % 1000000;
final otpString = otp.toString().padLeft(6, '0');
if (otpString == token) {
return true;
}
}
return false;
}
}
What's Next
You now have a comprehensive understanding of authentication and security in Hypermodern applications. The next chapter will focus on performance and optimization, covering techniques to maximize the efficiency of your applications across all transport protocols, including caching strategies, connection pooling, and monitoring.
No Comments