Skip to content

Authentication & Security

LDK provides comprehensive authentication and security features, from basic JWT authentication to advanced biometric security and token management.

Overview

LDK offers two levels of authentication:

  1. Basic Authentication (LDKAuth) - Standard JWT-based authentication
  2. Enhanced Authentication (LDKEnhancedAuth) - Advanced security features including biometric auth, token refresh, and security monitoring

Basic Authentication

Sign Up

Create a new user account:

try {
  final user = await LDK.instance.auth.signUp(
    email: 'user@example.com',
    password: 'securePassword123',
    username: 'johndoe', // Optional
    additionalData: { // Optional
      'firstName': 'John',
      'lastName': 'Doe',
      'phone': '+1234567890',
    },
  );

  print('User created: ${user.email}');
  print('JWT Token: ${user.jwt}');
} catch (e) {
  if (e is LDKAuthException) {
    print('Authentication error: ${e.message}');
  }
}

Sign In

Authenticate an existing user:

try {
  final user = await LDK.instance.auth.signIn(
    email: 'user@example.com',
    password: 'securePassword123',
  );

  // Token is automatically set for future requests
  print('Welcome back, ${user.email}!');
} catch (e) {
  print('Login failed: $e');
}

Sign Out

await LDK.instance.auth.signOut();
print('User signed out');

Authentication State

Monitor authentication state changes:

class AuthService {
  StreamSubscription? _authSubscription;
  StreamSubscription? _userSubscription;

  void startListening() {
    // Listen to authentication state
    _authSubscription = LDK.instance.auth.authStateStream.listen((isAuthenticated) {
      if (isAuthenticated) {
        print('User is authenticated');
      } else {
        print('User is not authenticated');
      }
    });

    // Listen to user changes
    _userSubscription = LDK.instance.auth.userStream.listen((user) {
      if (user != null) {
        print('Current user: ${user.email}');
      } else {
        print('No user logged in');
      }
    });
  }

  void dispose() {
    _authSubscription?.cancel();
    _userSubscription?.cancel();
  }
}

Current User

Get the currently authenticated user:

final user = LDK.instance.auth.currentUser;
if (user != null) {
  print('Current user: ${user.email}');
  print('User ID: ${user.id}');
  print('Username: ${user.username}');
} else {
  print('No user logged in');
}

// Check authentication status
if (LDK.instance.auth.isAuthenticated) {
  // User is logged in
}

Password Reset

// Request password reset
await LDK.instance.auth.forgotPassword(email: 'user@example.com');

// Reset password with code
final user = await LDK.instance.auth.resetPassword(
  code: 'reset-code-from-email',
  password: 'newPassword123',
  passwordConfirmation: 'newPassword123',
);

Update User Profile

final updatedUser = await LDK.instance.auth.updateUser({
  'firstName': 'Jane',
  'lastName': 'Smith',
  'phone': '+1987654321',
});

Enhanced Authentication (V2)

Enhanced authentication provides advanced security features:

Enable Enhanced Auth

await LDK.initialize(
  baseUrl: 'https://your-server.com',
  enableEnhancedAuth: true, // Enable enhanced features
);

final enhancedAuth = LDK.instance.enhancedAuth;

Automatic Token Refresh

Enhanced auth automatically refreshes JWT tokens before they expire:

// Listen to token refresh events
LDK.instance.enhancedAuth.tokenRefreshStream.listen((isRefreshing) {
  if (isRefreshing) {
    print('Refreshing authentication token...');
  } else {
    print('Token refresh completed');
  }
});

// Manual token refresh
try {
  await LDK.instance.enhancedAuth.refreshToken();
  print('Token refreshed successfully');
} catch (e) {
  print('Token refresh failed: $e');
  // Redirect to login
}

Biometric Authentication

Enable and use biometric authentication:

class BiometricAuthService {
  Future<bool> setupBiometric() async {
    try {
      // Check if biometric is available
      final isAvailable = await LDK.instance.enhancedAuth.isBiometricAvailable();
      if (!isAvailable) {
        print('Biometric authentication not available');
        return false;
      }

      // Enable biometric authentication
      await LDK.instance.enhancedAuth.enableBiometric(
        reason: 'Enable biometric authentication for secure access',
      );

      print('Biometric authentication enabled');
      return true;
    } catch (e) {
      print('Failed to setup biometric: $e');
      return false;
    }
  }

  Future<bool> signInWithBiometric() async {
    try {
      final user = await LDK.instance.enhancedAuth.signInWithBiometric(
        reason: 'Authenticate with your fingerprint or face',
      );

      print('Biometric authentication successful: ${user.email}');
      return true;
    } catch (e) {
      print('Biometric authentication failed: $e');
      return false;
    }
  }

  Future<void> disableBiometric() async {
    await LDK.instance.enhancedAuth.disableBiometric();
    print('Biometric authentication disabled');
  }
}

Security Event Monitoring

Monitor security events and potential threats:

class SecurityMonitor {
  StreamSubscription? _securitySubscription;

  void startMonitoring() {
    _securitySubscription = LDK.instance.enhancedAuth.securityEventsStream.listen((event) {
      if (event != null) {
        _handleSecurityEvent(event);
      }
    });
  }

  void _handleSecurityEvent(SecurityEvent event) {
    switch (event.type) {
      case SecurityEventType.suspiciousLogin:
        _handleSuspiciousLogin(event);
        break;
      case SecurityEventType.multipleFailedAttempts:
        _handleFailedAttempts(event);
        break;
      case SecurityEventType.tokenExpired:
        _handleTokenExpired(event);
        break;
      case SecurityEventType.deviceChanged:
        _handleDeviceChanged(event);
        break;
    }
  }

  void _handleSuspiciousLogin(SecurityEvent event) {
    // Show security alert
    showDialog(
      context: context,
      builder: (context) => AlertDialog(
        title: Text('Security Alert'),
        content: Text('Suspicious login detected from ${event.metadata['location']}'),
        actions: [
          TextButton(
            onPressed: () => _reportSecurity(event),
            child: Text('Report'),
          ),
          TextButton(
            onPressed: () => Navigator.pop(context),
            child: Text('Dismiss'),
          ),
        ],
      ),
    );
  }

  void _handleFailedAttempts(SecurityEvent event) {
    final attempts = event.metadata['attempts'] as int;
    if (attempts >= 5) {
      // Lock account temporarily
      _lockAccount();
    }
  }

  void dispose() {
    _securitySubscription?.cancel();
  }
}

Session Management

Advanced session management features:

class SessionManager {
  Future<void> extendSession() async {
    try {
      await LDK.instance.enhancedAuth.extendSession();
      print('Session extended');
    } catch (e) {
      print('Failed to extend session: $e');
    }
  }

  Future<void> terminateAllSessions() async {
    try {
      await LDK.instance.enhancedAuth.terminateAllSessions();
      print('All sessions terminated');
    } catch (e) {
      print('Failed to terminate sessions: $e');
    }
  }

  Future<List<ActiveSession>> getActiveSessions() async {
    try {
      final sessions = await LDK.instance.enhancedAuth.getActiveSessions();
      return sessions;
    } catch (e) {
      print('Failed to get active sessions: $e');
      return [];
    }
  }
}

Authentication UI Components

Login Form

class LoginForm extends StatefulWidget {
  @override
  _LoginFormState createState() => _LoginFormState();
}

class _LoginFormState extends State<LoginForm> {
  final _formKey = GlobalKey<FormState>();
  final _emailController = TextEditingController();
  final _passwordController = TextEditingController();
  bool _isLoading = false;
  bool _rememberMe = false;

  @override
  Widget build(BuildContext context) {
    return Form(
      key: _formKey,
      child: Column(
        children: [
          TextFormField(
            controller: _emailController,
            decoration: InputDecoration(
              labelText: 'Email',
              prefixIcon: Icon(Icons.email),
            ),
            keyboardType: TextInputType.emailAddress,
            validator: (value) {
              if (value == null || value.isEmpty) {
                return 'Please enter your email';
              }
              if (!value.contains('@')) {
                return 'Please enter a valid email';
              }
              return null;
            },
          ),
          SizedBox(height: 16),
          TextFormField(
            controller: _passwordController,
            decoration: InputDecoration(
              labelText: 'Password',
              prefixIcon: Icon(Icons.lock),
            ),
            obscureText: true,
            validator: (value) {
              if (value == null || value.isEmpty) {
                return 'Please enter your password';
              }
              return null;
            },
          ),
          SizedBox(height: 16),
          Row(
            children: [
              Checkbox(
                value: _rememberMe,
                onChanged: (value) => setState(() => _rememberMe = value ?? false),
              ),
              Text('Remember me'),
              Spacer(),
              TextButton(
                onPressed: _forgotPassword,
                child: Text('Forgot Password?'),
              ),
            ],
          ),
          SizedBox(height: 24),
          SizedBox(
            width: double.infinity,
            child: ElevatedButton(
              onPressed: _isLoading ? null : _signIn,
              child: _isLoading
                  ? CircularProgressIndicator()
                  : Text('Sign In'),
            ),
          ),
          SizedBox(height: 16),
          if (await _isBiometricAvailable()) ...[
            Divider(),
            SizedBox(height: 16),
            SizedBox(
              width: double.infinity,
              child: OutlinedButton.icon(
                onPressed: _signInWithBiometric,
                icon: Icon(Icons.fingerprint),
                label: Text('Sign in with Biometric'),
              ),
            ),
          ],
        ],
      ),
    );
  }

  Future<bool> _isBiometricAvailable() async {
    try {
      return await LDK.instance.enhancedAuth.isBiometricAvailable();
    } catch (e) {
      return false;
    }
  }

  Future<void> _signIn() async {
    if (!_formKey.currentState!.validate()) return;

    setState(() => _isLoading = true);

    try {
      final user = await LDK.instance.auth.signIn(
        email: _emailController.text,
        password: _passwordController.text,
      );

      // Navigate to home screen
      Navigator.pushReplacementNamed(context, '/home');
    } catch (e) {
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text('Login failed: $e')),
      );
    } finally {
      setState(() => _isLoading = false);
    }
  }

  Future<void> _signInWithBiometric() async {
    try {
      final user = await LDK.instance.enhancedAuth.signInWithBiometric(
        reason: 'Authenticate to access your account',
      );

      Navigator.pushReplacementNamed(context, '/home');
    } catch (e) {
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text('Biometric authentication failed: $e')),
      );
    }
  }

  void _forgotPassword() {
    // Navigate to forgot password screen
    Navigator.pushNamed(context, '/forgot-password');
  }

  @override
  void dispose() {
    _emailController.dispose();
    _passwordController.dispose();
    super.dispose();
  }
}

Authentication Guard

Protect routes that require authentication:

class AuthGuard extends StatelessWidget {
  final Widget child;
  final Widget? fallback;

  const AuthGuard({
    Key? key,
    required this.child,
    this.fallback,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return StreamBuilder<bool>(
      stream: LDK.instance.auth.authStateStream,
      initialData: LDK.instance.auth.isAuthenticated,
      builder: (context, snapshot) {
        final isAuthenticated = snapshot.data ?? false;

        if (isAuthenticated) {
          return child;
        } else {
          return fallback ?? LoginScreen();
        }
      },
    );
  }
}

// Usage
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: AuthGuard(
        child: HomeScreen(),
        fallback: LoginScreen(),
      ),
    );
  }
}

Best Practices

1. Secure Token Storage

LDK automatically stores tokens securely using flutter_secure_storage, but you can customize this:

// Tokens are automatically stored securely
// No additional configuration needed

2. Handle Authentication Errors

Future<void> signInWithErrorHandling(String email, String password) async {
  try {
    await LDK.instance.auth.signIn(email: email, password: password);
  } on LDKAuthException catch (e) {
    switch (e.code) {
      case 'invalid_credentials':
        showError('Invalid email or password');
        break;
      case 'account_locked':
        showError('Account is temporarily locked');
        break;
      case 'email_not_verified':
        showError('Please verify your email first');
        break;
      default:
        showError('Authentication failed: ${e.message}');
    }
  } catch (e) {
    showError('An unexpected error occurred');
  }
}

3. Implement Auto-Login

class AuthService {
  Future<bool> tryAutoLogin() async {
    try {
      // LDK automatically restores authentication state
      // Just check if user is authenticated
      return LDK.instance.auth.isAuthenticated;
    } catch (e) {
      return false;
    }
  }
}

4. Logout Cleanup

Future<void> signOut() async {
  try {
    // Sign out from LDK
    await LDK.instance.auth.signOut();

    // Clear app-specific data
    await _clearAppData();

    // Navigate to login screen
    Navigator.pushNamedAndRemoveUntil(
      context,
      '/login',
      (route) => false,
    );
  } catch (e) {
    print('Logout error: $e');
  }
}

5. Biometric Setup Flow

class BiometricSetupFlow extends StatefulWidget {
  @override
  _BiometricSetupFlowState createState() => _BiometricSetupFlowState();
}

class _BiometricSetupFlowState extends State<BiometricSetupFlow> {
  bool _isAvailable = false;
  bool _isEnabled = false;

  @override
  void initState() {
    super.initState();
    _checkBiometricStatus();
  }

  Future<void> _checkBiometricStatus() async {
    final available = await LDK.instance.enhancedAuth.isBiometricAvailable();
    final enabled = await LDK.instance.enhancedAuth.isBiometricEnabled();

    setState(() {
      _isAvailable = available;
      _isEnabled = enabled;
    });
  }

  @override
  Widget build(BuildContext context) {
    if (!_isAvailable) {
      return Card(
        child: ListTile(
          leading: Icon(Icons.fingerprint, color: Colors.grey),
          title: Text('Biometric Authentication'),
          subtitle: Text('Not available on this device'),
        ),
      );
    }

    return Card(
      child: SwitchListTile(
        secondary: Icon(Icons.fingerprint),
        title: Text('Biometric Authentication'),
        subtitle: Text(_isEnabled 
          ? 'Use fingerprint or face to sign in'
          : 'Enable for quick and secure access'),
        value: _isEnabled,
        onChanged: _toggleBiometric,
      ),
    );
  }

  Future<void> _toggleBiometric(bool enable) async {
    try {
      if (enable) {
        await LDK.instance.enhancedAuth.enableBiometric(
          reason: 'Enable biometric authentication for secure access',
        );
      } else {
        await LDK.instance.enhancedAuth.disableBiometric();
      }

      await _checkBiometricStatus();
    } catch (e) {
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text('Failed to ${enable ? 'enable' : 'disable'} biometric: $e')),
      );
    }
  }
}

Security Considerations

1. Token Expiration

// Enhanced auth automatically handles token refresh
// But you can also handle it manually
LDK.instance.enhancedAuth.tokenRefreshStream.listen((isRefreshing) {
  if (!isRefreshing) {
    // Token refresh completed or failed
    if (!LDK.instance.auth.isAuthenticated) {
      // Redirect to login
      Navigator.pushNamedAndRemoveUntil(context, '/login', (route) => false);
    }
  }
});

2. Secure Communication

// LDK automatically uses HTTPS for secure communication
// Ensure your Strapi server also uses HTTPS in production
await LDK.initialize(
  baseUrl: 'https://your-secure-server.com', // Always use HTTPS
);

3. Biometric Fallback

Future<bool> authenticateUser() async {
  // Try biometric first
  if (await LDK.instance.enhancedAuth.isBiometricEnabled()) {
    try {
      await LDK.instance.enhancedAuth.signInWithBiometric(
        reason: 'Authenticate to access your account',
      );
      return true;
    } catch (e) {
      // Biometric failed, fall back to password
      return await _showPasswordDialog();
    }
  }

  // No biometric, use password
  return await _showPasswordDialog();
}

Next Steps


Next: CRUD Operations →