Skip to main content

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

  1. Multi-layered Security: Combine authentication, authorization, and audit logging
  2. Flexible Permissions: Use RBAC for structure and ABAC for fine-grained control
  3. Strong Authentication: Implement MFA and consider passwordless options
  4. Comprehensive Logging: Log all security events for compliance and monitoring
  5. 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