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:
- Basic Authentication (
LDKAuth) - Standard JWT-based authentication - 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¶
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:
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¶
- CRUD Operations - Learn about data operations
- Real-time Updates - Implement live data synchronization
- Offline Support - Build offline-first applications
- API Reference - Authentication - Complete API documentation
Next: CRUD Operations →