Chapter 9: Access Control and Authentication
Overview
Vektagraf implements sophisticated access control mechanisms that support both Role-Based Access Control (RBAC) and Attribute-Based Access Control (ABAC). This chapter covers authentication systems, permission models, multi-factor authentication, and comprehensive audit logging for compliance tracking.
Learning Objectives
By the end of this chapter, you will understand:
- RBAC and ABAC implementation patterns in Vektagraf
- Multi-factor authentication and token management
- Permission systems and policy enforcement
- Object-level and field-level security policies
- Audit logging and compliance tracking
- Session management and security contexts
Prerequisites
- Understanding of security concepts (Chapter 8)
- Familiarity with Vektagraf schema design (Chapter 3)
- Knowledge of database operations (Chapter 4)
Authentication Architecture
Vektagraf supports multiple authentication methods and can integrate with external identity providers.
Authentication Flow
sequenceDiagram
participant C as Client
participant A as Auth Service
participant V as Vektagraf
participant I as Identity Provider
C->>A: Login Request
A->>I: Validate Credentials
I->>A: User Info + Roles
A->>A: Generate Security Context
A->>V: Create Session
V->>A: Session Token
A->>C: Authentication Response
C->>V: API Request + Token
V->>V: Validate Session
V->>V: Check Permissions
V->>C: Response
Basic Authentication Setup
import 'package:vektagraf/vektagraf.dart';
// Configure authentication
final authConfig = AuthenticationConfig(
providers: [
LocalAuthProvider(
passwordPolicy: PasswordPolicy(
minLength: 12,
requireSpecialChars: true,
requireNumbers: true,
requireUppercase: true,
maxAge: Duration(days: 90),
),
),
OAuthProvider(
provider: 'google',
clientId: 'your-client-id',
clientSecret: 'your-client-secret',
scopes: ['openid', 'profile', 'email'],
),
SamlProvider(
entityId: 'your-entity-id',
ssoUrl: 'https://idp.example.com/sso',
certificate: samlCertificate,
),
],
multiFactorRequired: true,
sessionTimeout: Duration(hours: 8),
);
final database = await Vektagraf.open(
'secure_app',
authConfig: authConfig,
);
Role-Based Access Control (RBAC)
RBAC provides a structured approach to managing permissions through roles and hierarchies.
Role Definition
Define roles in your security configuration:
{
"security": {
"roles": {
"admin": {
"name": "Administrator",
"description": "Full system access",
"permissions": ["*"],
"inherits": []
},
"manager": {
"name": "Manager",
"description": "Management access",
"permissions": [
"read:*",
"write:users",
"write:reports",
"delete:reports"
],
"inherits": ["employee"]
},
"employee": {
"name": "Employee",
"description": "Basic employee access",
"permissions": [
"read:users",
"read:reports",
"write:own_profile"
],
"inherits": []
},
"guest": {
"name": "Guest",
"description": "Read-only access",
"permissions": [
"read:public_content"
],
"inherits": []
}
}
}
}
Role Implementation
// Create security policy engine
final securityEngine = SecurityPolicyEngine(securityConfig);
// Create user with roles
final user = User()
..id = 'user-123'
..email = 'john@example.com'
..roles = ['employee', 'manager'];
// Create security context
final context = SecurityContext(
userId: user.id,
roles: user.roles,
tenantId: 'tenant-456',
createdAt: DateTime.now(),
expiresAt: DateTime.now().add(Duration(hours: 8)),
);
// Check permissions
final permission = securityEngine.checkPermission(
context,
'users',
PermissionType.read,
);
if (permission.granted) {
// Perform operation
final users = await database.users.findAll();
} else {
throw SecurityException('Access denied: ${permission.reason}');
}
Role Hierarchy
// Define role hierarchy
class RoleHierarchy {
final Map<String, Set<String>> _hierarchy = {
'admin': {'manager', 'employee', 'guest'},
'manager': {'employee', 'guest'},
'employee': {'guest'},
'guest': {},
};
bool hasRole(List<String> userRoles, String requiredRole) {
// Check direct role assignment
if (userRoles.contains(requiredRole)) return true;
// Check inherited roles
for (final userRole in userRoles) {
final inheritedRoles = _hierarchy[userRole] ?? {};
if (inheritedRoles.contains(requiredRole)) return true;
}
return false;
}
Set<String> getAllRoles(List<String> userRoles) {
final allRoles = <String>{};
for (final role in userRoles) {
allRoles.add(role);
allRoles.addAll(_hierarchy[role] ?? {});
}
return allRoles;
}
}
Attribute-Based Access Control (ABAC)
ABAC provides fine-grained access control based on attributes of users, resources, and environment.
ABAC Policy Definition
{
"security": {
"abacPolicies": [
{
"name": "department_access",
"description": "Users can only access their department's data",
"condition": "user.department == resource.department",
"effect": "allow",
"resources": ["employees", "projects"]
},
{
"name": "time_based_access",
"description": "Restrict access to business hours",
"condition": "environment.time >= '09:00' && environment.time <= '17:00'",
"effect": "allow",
"resources": ["*"]
},
{
"name": "location_based_access",
"description": "Restrict access from specific locations",
"condition": "user.location in ['office', 'home'] || user.role == 'admin'",
"effect": "allow",
"resources": ["sensitive_data"]
}
]
}
}
ABAC Implementation
// ABAC policy evaluator
class AbacPolicyEvaluator {
final List<AbacPolicy> policies;
AbacPolicyEvaluator(this.policies);
bool evaluateAccess({
required Map<String, dynamic> userAttributes,
required Map<String, dynamic> resourceAttributes,
required Map<String, dynamic> environmentAttributes,
required String action,
required String resource,
}) {
for (final policy in policies) {
if (!policy.appliesTo(resource)) continue;
final context = PolicyContext(
user: userAttributes,
resource: resourceAttributes,
environment: environmentAttributes,
action: action,
);
final result = policy.evaluate(context);
if (result == PolicyEffect.deny) return false;
if (result == PolicyEffect.allow) return true;
}
// Default deny
return false;
}
}
// Usage example
final evaluator = AbacPolicyEvaluator(abacPolicies);
final hasAccess = evaluator.evaluateAccess(
userAttributes: {
'userId': 'user-123',
'department': 'engineering',
'location': 'office',
'clearanceLevel': 'secret',
},
resourceAttributes: {
'resourceType': 'project',
'department': 'engineering',
'classification': 'confidential',
},
environmentAttributes: {
'time': '14:30',
'ipAddress': '192.168.1.100',
'requestType': 'read',
},
action: 'read',
resource: 'projects',
);
Multi-Factor Authentication
Vektagraf supports various MFA methods for enhanced security.
MFA Configuration
// Configure MFA providers
final mfaConfig = MfaConfig(
providers: [
TotpProvider(
issuer: 'MyApp',
algorithm: 'SHA256',
digits: 6,
period: 30,
),
SmsProvider(
provider: 'twilio',
apiKey: 'your-api-key',
fromNumber: '+1234567890',
),
EmailProvider(
smtpConfig: SmtpConfig(
host: 'smtp.example.com',
port: 587,
username: 'noreply@example.com',
password: 'smtp-password',
),
),
WebAuthnProvider(
rpId: 'example.com',
rpName: 'My Application',
requireResidentKey: false,
userVerification: 'preferred',
),
],
backupCodes: BackupCodesConfig(
enabled: true,
codeLength: 8,
codeCount: 10,
),
);
MFA Implementation
// MFA enrollment
class MfaManager {
final List<MfaProvider> providers;
MfaManager(this.providers);
Future<MfaEnrollmentResult> enrollUser(
String userId,
MfaMethod method,
) async {
final provider = _getProvider(method);
switch (method) {
case MfaMethod.totp:
final secret = await provider.generateSecret();
final qrCode = await provider.generateQrCode(userId, secret);
return MfaEnrollmentResult(
method: method,
secret: secret,
qrCodeUrl: qrCode,
backupCodes: await _generateBackupCodes(userId),
);
case MfaMethod.sms:
final phoneNumber = await _getUserPhoneNumber(userId);
await provider.sendVerificationCode(phoneNumber);
return MfaEnrollmentResult(
method: method,
message: 'Verification code sent to ${_maskPhoneNumber(phoneNumber)}',
);
case MfaMethod.webauthn:
final challenge = await provider.generateChallenge(userId);
return MfaEnrollmentResult(
method: method,
challenge: challenge,
timeout: Duration(minutes: 5),
);
}
}
Future<bool> verifyMfa(
String userId,
MfaMethod method,
String code,
) async {
final provider = _getProvider(method);
// Check backup codes first
if (await _isBackupCode(userId, code)) {
await _invalidateBackupCode(userId, code);
return true;
}
return await provider.verify(userId, code);
}
}
WebAuthn Implementation
// WebAuthn for passwordless authentication
class WebAuthnProvider extends MfaProvider {
final String rpId;
final String rpName;
WebAuthnProvider({
required this.rpId,
required this.rpName,
});
Future<WebAuthnChallenge> generateRegistrationChallenge(
String userId,
) async {
final challenge = _generateSecureChallenge();
return WebAuthnChallenge(
challenge: challenge,
rp: RelyingParty(id: rpId, name: rpName),
user: WebAuthnUser(
id: userId,
name: await _getUserEmail(userId),
displayName: await _getUserDisplayName(userId),
),
pubKeyCredParams: [
PubKeyCredParam(type: 'public-key', alg: -7), // ES256
PubKeyCredParam(type: 'public-key', alg: -257), // RS256
],
authenticatorSelection: AuthenticatorSelection(
authenticatorAttachment: 'platform',
userVerification: 'required',
),
timeout: Duration(minutes: 5),
);
}
Future<bool> verifyRegistration(
String userId,
WebAuthnRegistrationResponse response,
) async {
// Verify attestation and store credential
final isValid = await _verifyAttestation(response);
if (isValid) {
await _storeCredential(userId, response.credential);
}
return isValid;
}
Future<WebAuthnChallenge> generateAuthenticationChallenge(
String userId,
) async {
final credentials = await _getUserCredentials(userId);
final challenge = _generateSecureChallenge();
return WebAuthnChallenge(
challenge: challenge,
allowCredentials: credentials,
userVerification: 'required',
timeout: Duration(minutes: 5),
);
}
}
Permission Systems and Policy Enforcement
Implement comprehensive permission checking and policy enforcement.
Object-Level Security
// Object-level security policies
class ObjectSecurityPolicy {
final String name;
final String modelName;
final String condition;
final List<String> applicableRoles;
ObjectSecurityPolicy({
required this.name,
required this.modelName,
required this.condition,
this.applicableRoles = const [],
});
bool evaluate(Map<String, dynamic> object, SecurityContext context) {
// Tenant isolation
if (condition.contains('tenant_id = \$tenantId')) {
final objectTenantId = object['tenant_id'] ?? object['tenantId'];
return objectTenantId == context.tenantId;
}
// Owner-based access
if (condition.contains('owner_id = \$userId')) {
final ownerId = object['owner_id'] ?? object['ownerId'];
return ownerId == context.userId;
}
// Department-based access
if (condition.contains('department = \$userDepartment')) {
final objectDept = object['department'];
final userDept = context.attributes['department'];
return objectDept == userDept;
}
// Time-based access
if (condition.contains('created_at > \$startDate')) {
final createdAt = DateTime.parse(object['created_at'] as String);
final startDate = context.attributes['accessStartDate'] as DateTime?;
return startDate == null || createdAt.isAfter(startDate);
}
return true;
}
}
Field-Level Security
// Field-level access control
class FieldAccessControl {
final String fieldName;
final List<String> readRoles;
final List<String> writeRoles;
final String? maskingPattern;
final bool encrypted;
FieldAccessControl({
required this.fieldName,
this.readRoles = const [],
this.writeRoles = const [],
this.maskingPattern,
this.encrypted = false,
});
bool canRead(SecurityContext context) {
if (readRoles.isEmpty) return true;
return context.hasAnyRole(readRoles);
}
bool canWrite(SecurityContext context) {
if (writeRoles.isEmpty) return true;
return context.hasAnyRole(writeRoles);
}
dynamic maskValue(dynamic value, SecurityContext context) {
if (canRead(context)) return value;
if (maskingPattern != null) {
if (value is String) {
switch (maskingPattern) {
case 'email':
return _maskEmail(value);
case 'phone':
return _maskPhoneNumber(value);
case 'ssn':
return 'XXX-XX-${value.substring(value.length - 4)}';
case 'credit_card':
return '**** **** **** ${value.substring(value.length - 4)}';
default:
return maskingPattern;
}
}
}
return null; // Hide field completely
}
String _maskEmail(String email) {
final parts = email.split('@');
if (parts.length != 2) return '***@***.***';
final username = parts[0];
final domain = parts[1];
final maskedUsername = username.length > 2
? '${username[0]}***${username[username.length - 1]}'
: '***';
return '$maskedUsername@$domain';
}
String _maskPhoneNumber(String phone) {
if (phone.length < 4) return '***';
return '***-***-${phone.substring(phone.length - 4)}';
}
}
Dynamic Permission Evaluation
// Dynamic permission evaluator
class PermissionEvaluator {
final SecurityPolicyEngine policyEngine;
final AuditLogger auditLogger;
PermissionEvaluator({
required this.policyEngine,
required this.auditLogger,
});
Future<PermissionResult> evaluatePermission({
required SecurityContext context,
required String action,
required String resourceType,
String? resourceId,
Map<String, dynamic>? resourceData,
Map<String, dynamic>? additionalContext,
}) async {
try {
// Check if context is valid
if (context.isExpired) {
return PermissionResult.denied('Security context has expired');
}
// Check global permissions
final globalResult = policyEngine.checkPermission(
context,
resourceType,
_actionToPermissionType(action),
);
if (!globalResult.granted) {
await _logPermissionDenied(context, action, resourceType, globalResult.reason);
return globalResult;
}
// Check object-level permissions if resource data provided
if (resourceData != null) {
final objectResult = _checkObjectLevelPermissions(
context,
resourceType,
resourceData,
);
if (!objectResult.granted) {
await _logPermissionDenied(context, action, resourceType, objectResult.reason);
return objectResult;
}
}
// Check ABAC policies if additional context provided
if (additionalContext != null) {
final abacResult = _checkAbacPolicies(
context,
action,
resourceType,
resourceData ?? {},
additionalContext,
);
if (!abacResult.granted) {
await _logPermissionDenied(context, action, resourceType, abacResult.reason);
return abacResult;
}
}
// Log successful permission check
await auditLogger.logOperation(
eventType: AuditEventType.read,
userId: context.userId,
action: 'permission_granted',
resourceType: resourceType,
resourceId: resourceId,
tenantId: context.tenantId,
details: {
'action': action,
'roles': context.roles,
},
);
return PermissionResult.granted('Permission granted');
} catch (e) {
await auditLogger.logOperation(
eventType: AuditEventType.permissionDenied,
userId: context.userId,
action: 'permission_error',
resourceType: resourceType,
resourceId: resourceId,
tenantId: context.tenantId,
success: false,
errorMessage: e.toString(),
);
return PermissionResult.denied('Permission evaluation error: $e');
}
}
Future<void> _logPermissionDenied(
SecurityContext context,
String action,
String resourceType,
String reason,
) async {
await auditLogger.logPermissionDenied(
userId: context.userId,
action: action,
resourceType: resourceType,
tenantId: context.tenantId,
reason: reason,
details: {
'roles': context.roles,
'attributes': context.attributes,
},
);
}
}
Session Management
Implement secure session management with proper lifecycle handling.
Session Configuration
// Session manager configuration
final sessionManager = SessionManager(
config: SessionConfig(
timeout: Duration(hours: 8),
renewalThreshold: Duration(minutes: 30),
maxConcurrentSessions: 3,
enableSessionFixationProtection: true,
secureSessionCookies: true,
httpOnlySessionCookies: true,
),
storage: RedisSessionStorage(
host: 'redis.example.com',
port: 6379,
password: 'redis-password',
database: 0,
),
);
Session Implementation
// Session management
class SessionManager {
final SessionConfig config;
final SessionStorage storage;
final Map<String, SecurityContext> _activeSessions = {};
SessionManager({
required this.config,
required this.storage,
});
Future<SessionResult> createSession({
required String userId,
required List<String> roles,
String? tenantId,
Map<String, dynamic> attributes = const {},
String? ipAddress,
String? userAgent,
}) async {
// Check concurrent session limit
final existingSessions = await _getUserSessions(userId);
if (existingSessions.length >= config.maxConcurrentSessions) {
// Terminate oldest session
await _terminateOldestSession(userId);
}
// Generate secure session ID
final sessionId = _generateSecureSessionId();
// Create security context
final context = SecurityContext(
userId: userId,
roles: roles,
tenantId: tenantId,
attributes: {
...attributes,
'sessionId': sessionId,
'ipAddress': ipAddress,
'userAgent': userAgent,
'createdAt': DateTime.now().toIso8601String(),
},
createdAt: DateTime.now(),
expiresAt: DateTime.now().add(config.timeout),
);
// Store session
await storage.storeSession(sessionId, context);
_activeSessions[sessionId] = context;
return SessionResult(
sessionId: sessionId,
context: context,
expiresAt: context.expiresAt!,
);
}
Future<SecurityContext?> getSession(String sessionId) async {
// Check memory cache first
var context = _activeSessions[sessionId];
if (context == null) {
// Load from storage
context = await storage.getSession(sessionId);
if (context != null) {
_activeSessions[sessionId] = context;
}
}
if (context == null || context.isExpired) {
await terminateSession(sessionId);
return null;
}
// Check if session needs renewal
if (_shouldRenewSession(context)) {
context = await _renewSession(sessionId, context);
}
return context;
}
Future<void> terminateSession(String sessionId) async {
await storage.deleteSession(sessionId);
_activeSessions.remove(sessionId);
}
Future<void> terminateAllUserSessions(String userId) async {
final sessions = await _getUserSessions(userId);
for (final sessionId in sessions) {
await terminateSession(sessionId);
}
}
bool _shouldRenewSession(SecurityContext context) {
final expiresAt = context.expiresAt;
if (expiresAt == null) return false;
final timeUntilExpiry = expiresAt.difference(DateTime.now());
return timeUntilExpiry <= config.renewalThreshold;
}
Future<SecurityContext> _renewSession(
String sessionId,
SecurityContext context,
) async {
final renewedContext = SecurityContext(
userId: context.userId,
roles: context.roles,
tenantId: context.tenantId,
attributes: context.attributes,
createdAt: context.createdAt,
expiresAt: DateTime.now().add(config.timeout),
);
await storage.storeSession(sessionId, renewedContext);
_activeSessions[sessionId] = renewedContext;
return renewedContext;
}
}
Audit Logging and Compliance
Comprehensive audit logging for security events and compliance requirements.
Audit Configuration
// Configure comprehensive audit logging
final auditLogger = AuditLogger(
store: DatabaseAuditLogStore(database),
enabled: true,
loggedEventTypes: {
AuditEventType.login,
AuditEventType.logout,
AuditEventType.permissionDenied,
AuditEventType.read,
AuditEventType.write,
AuditEventType.delete,
AuditEventType.securityPolicyChange,
},
);
Security Event Logging
// Security-specific audit events
class SecurityAuditLogger extends AuditLogger {
Future<void> logAuthenticationAttempt({
required String userId,
required String method,
required bool success,
String? ipAddress,
String? userAgent,
String? failureReason,
}) async {
await logOperation(
eventType: success ? AuditEventType.login : AuditEventType.permissionDenied,
userId: userId,
action: 'authentication_attempt',
details: {
'method': method,
'ipAddress': ipAddress,
'userAgent': userAgent,
if (!success) 'failureReason': failureReason,
},
success: success,
errorMessage: success ? null : failureReason,
severity: success ? AuditSeverity.info : AuditSeverity.warning,
);
}
Future<void> logMfaEvent({
required String userId,
required String method,
required String action,
required bool success,
String? details,
}) async {
await logOperation(
eventType: AuditEventType.securityPolicyChange,
userId: userId,
action: 'mfa_$action',
details: {
'method': method,
'details': details,
},
success: success,
severity: success ? AuditSeverity.info : AuditSeverity.warning,
);
}
Future<void> logRoleChange({
required String adminUserId,
required String targetUserId,
required List<String> oldRoles,
required List<String> newRoles,
String? reason,
}) async {
await logOperation(
eventType: AuditEventType.securityPolicyChange,
userId: adminUserId,
action: 'role_change',
details: {
'targetUserId': targetUserId,
'oldRoles': oldRoles,
'newRoles': newRoles,
'reason': reason,
},
beforeState: {'roles': oldRoles},
afterState: {'roles': newRoles},
severity: AuditSeverity.warning,
);
}
Future<void> logPrivilegeEscalation({
required String userId,
required String attemptedAction,
required String resourceType,
String? resourceId,
required List<String> requiredRoles,
required List<String> userRoles,
}) async {
await logOperation(
eventType: AuditEventType.permissionDenied,
userId: userId,
action: 'privilege_escalation_attempt',
resourceType: resourceType,
resourceId: resourceId,
details: {
'attemptedAction': attemptedAction,
'requiredRoles': requiredRoles,
'userRoles': userRoles,
},
success: false,
severity: AuditSeverity.error,
);
}
}
Compliance Reporting
// Generate compliance reports
class ComplianceReporter {
final AuditLogger auditLogger;
ComplianceReporter(this.auditLogger);
Future<AccessControlReport> generateAccessControlReport({
DateTime? startDate,
DateTime? endDate,
String? tenantId,
}) async {
final start = startDate ?? DateTime.now().subtract(Duration(days: 30));
final end = endDate ?? DateTime.now();
// Get authentication events
final authEvents = await auditLogger.queryLogs(
eventType: AuditEventType.login,
startTime: start,
endTime: end,
tenantId: tenantId,
limit: 10000,
);
// Get permission denied events
final deniedEvents = await auditLogger.queryLogs(
eventType: AuditEventType.permissionDenied,
startTime: start,
endTime: end,
tenantId: tenantId,
limit: 10000,
);
// Get role changes
final roleChanges = await auditLogger.queryLogs(
startTime: start,
endTime: end,
tenantId: tenantId,
limit: 1000,
);
final roleChangeEvents = roleChanges
.where((log) => log.action == 'role_change')
.toList();
return AccessControlReport(
reportId: _generateReportId(),
generatedAt: DateTime.now(),
periodStart: start,
periodEnd: end,
tenantId: tenantId,
authenticationEvents: _analyzeAuthEvents(authEvents),
permissionDeniedEvents: _analyzePermissionEvents(deniedEvents),
roleChanges: _analyzeRoleChanges(roleChangeEvents),
complianceScore: _calculateComplianceScore(authEvents, deniedEvents),
);
}
Map<String, dynamic> _analyzeAuthEvents(List<AuditLogEntry> events) {
final successfulLogins = events.where((e) => e.success).length;
final failedLogins = events.where((e) => !e.success).length;
final userLoginCounts = <String, int>{};
final methodCounts = <String, int>{};
for (final event in events) {
userLoginCounts[event.userId] =
(userLoginCounts[event.userId] ?? 0) + 1;
final method = event.details['method'] as String? ?? 'unknown';
methodCounts[method] = (methodCounts[method] ?? 0) + 1;
}
return {
'totalAttempts': events.length,
'successfulLogins': successfulLogins,
'failedLogins': failedLogins,
'successRate': events.isEmpty ? 0.0 : successfulLogins / events.length,
'uniqueUsers': userLoginCounts.length,
'methodBreakdown': methodCounts,
'topUsers': _getTopUsers(userLoginCounts, 10),
};
}
Map<String, dynamic> _analyzePermissionEvents(List<AuditLogEntry> events) {
final resourceCounts = <String, int>{};
final actionCounts = <String, int>{};
final userCounts = <String, int>{};
for (final event in events) {
if (event.resourceType != null) {
resourceCounts[event.resourceType!] =
(resourceCounts[event.resourceType!] ?? 0) + 1;
}
actionCounts[event.action] = (actionCounts[event.action] ?? 0) + 1;
userCounts[event.userId] = (userCounts[event.userId] ?? 0) + 1;
}
return {
'totalDenials': events.length,
'resourceBreakdown': resourceCounts,
'actionBreakdown': actionCounts,
'topDeniedUsers': _getTopUsers(userCounts, 10),
'escalationAttempts': events
.where((e) => e.action == 'privilege_escalation_attempt')
.length,
};
}
}
Advanced Security Features
Anomaly Detection
// Detect unusual access patterns
class SecurityAnomalyDetector {
final AuditLogger auditLogger;
final Duration analysisWindow;
SecurityAnomalyDetector({
required this.auditLogger,
this.analysisWindow = const Duration(hours: 24),
});
Future<List<SecurityAnomaly>> detectAnomalies({
String? userId,
String? tenantId,
}) async {
final anomalies = <SecurityAnomaly>[];
// Get recent logs
final logs = await auditLogger.queryLogs(
userId: userId,
tenantId: tenantId,
startTime: DateTime.now().subtract(analysisWindow),
limit: 10000,
);
// Detect unusual login times
anomalies.addAll(await _detectUnusualLoginTimes(logs));
// Detect unusual access patterns
anomalies.addAll(await _detectUnusualAccessPatterns(logs));
// Detect potential brute force attacks
anomalies.addAll(await _detectBruteForceAttempts(logs));
// Detect privilege escalation attempts
anomalies.addAll(await _detectPrivilegeEscalation(logs));
return anomalies;
}
Future<List<SecurityAnomaly>> _detectUnusualLoginTimes(
List<AuditLogEntry> logs,
) async {
final anomalies = <SecurityAnomaly>[];
final loginLogs = logs.where((log) =>
log.eventType == AuditEventType.login && log.success).toList();
// Group by user
final userLogins = <String, List<AuditLogEntry>>{};
for (final log in loginLogs) {
userLogins.putIfAbsent(log.userId, () => []).add(log);
}
for (final entry in userLogins.entries) {
final userId = entry.key;
final userLoginLogs = entry.value;
// Analyze typical login hours
final loginHours = userLoginLogs
.map((log) => log.timestamp.hour)
.toList();
final typicalHours = _calculateTypicalHours(loginHours);
// Check recent logins for anomalies
final recentLogins = userLoginLogs
.where((log) => log.timestamp
.isAfter(DateTime.now().subtract(Duration(hours: 6))))
.toList();
for (final login in recentLogins) {
if (!typicalHours.contains(login.timestamp.hour)) {
anomalies.add(SecurityAnomaly(
type: SecurityAnomalyType.unusualLoginTime,
userId: userId,
timestamp: login.timestamp,
severity: SecurityAnomalySeverity.medium,
description: 'Login at unusual time: ${login.timestamp.hour}:00',
details: {
'loginHour': login.timestamp.hour,
'typicalHours': typicalHours,
'ipAddress': login.details['ipAddress'],
},
));
}
}
}
return anomalies;
}
Set<int> _calculateTypicalHours(List<int> loginHours) {
final hourCounts = <int, int>{};
for (final hour in loginHours) {
hourCounts[hour] = (hourCounts[hour] ?? 0) + 1;
}
// Consider hours with more than 10% of logins as typical
final totalLogins = loginHours.length;
final threshold = (totalLogins * 0.1).ceil();
return hourCounts.entries
.where((entry) => entry.value >= threshold)
.map((entry) => entry.key)
.toSet();
}
}
Summary
Vektagraf's access control and authentication system provides:
- Flexible Authentication with support for multiple providers and MFA
- RBAC and ABAC for comprehensive permission management
- Object and Field-Level Security for granular access control
- Session Management with secure lifecycle handling
- Comprehensive Audit Logging for compliance and monitoring
- Anomaly Detection for identifying security threats
Key Takeaways
- Multi-layered Security: Combine authentication, authorization, and audit logging
- Flexible Permissions: Use RBAC for structure and ABAC for fine-grained control
- Strong Authentication: Implement MFA and consider passwordless options
- Comprehensive Logging: Log all security events for compliance and monitoring
- Proactive Monitoring: Use anomaly detection to identify potential threats
Next Steps
- Chapter 10: Learn about privacy-preserving features and compliance
- Chapter 11: Explore multi-tenant security architecture
- Chapter 13: Understand monitoring and observability
No Comments