# Secure Password Reset for Flutter Apps: A Complete Guide > Learn how to implement secure password reset flows in Flutter apps using deep links, with complete Flutter code examples and best practices. - Canonical: https://redirectly.app/blog/password-reset-flutter-apps - Site: [Redirectly](https://redirectly.app) — deferred deep linking for Flutter & React Native - Published: 2024-01-12 - Author: Dmytro Vyrych - Category: Security · 6 min read Implement secure password reset flows using temporary deep links that work perfectly with your Flutter app's deep linking system. Learn best practices for security, user experience, and implementation. ## Overview & Security Benefits **Why Use Deep Links for Password Reset?** Traditional password reset flows often redirect users to web pages, breaking the mobile app experience. With deep linking, users can reset their passwords directly within your Flutter app, maintaining context and providing a seamless experience. ### Security Benefits - Time-limited reset tokens (15-30 minutes) - One-time use tokens - Secure token generation - Rate limiting protection - HTTPS enforcement - Audit trail logging ### UX Benefits - Native app experience - No context switching - Maintained app state - Consistent UI/UX - Offline capability - Push notification integration ## Password Reset Flow ### Complete User Journey 1. **User Requests Reset** — User enters email in "Forgot Password" screen 2. **Token Generation** — Backend generates secure reset token and creates temporary link 3. **Email Delivery** — Reset link sent to user's email address 4. **Link Click & App Open** — User clicks link, app opens to password reset screen 5. **Password Update** — User enters new password, token is validated and consumed ## Backend Implementation ### 1. Password Reset Request Endpoint Create an endpoint that handles password reset requests: ```javascript app.post('/api/forgot-password', async (req, res) => { try { const { email } = req.body; // 1. Validate email exists const user = await findUserByEmail(email); if (!user) { // Don't reveal if email exists or not return res.json({ success: true, message: 'If the email exists, a reset link has been sent.' }); } // 2. Generate secure reset token const resetToken = crypto.randomBytes(32).toString('hex'); const expiresAt = new Date(Date.now() + 30 * 60 * 1000); // 30 minutes // 3. Store reset token await storeResetToken(user.id, resetToken, expiresAt); // 4. Create temporary reset link const resetLink = await createPasswordResetLink(user.id, resetToken); // 5. Send reset email await sendPasswordResetEmail(email, resetLink); res.json({ success: true, message: 'If the email exists, a reset link has been sent.' }); } catch (error) { console.error('Password reset request error:', error); res.status(500).json({ error: 'Internal server error' }); } }); ``` ### 2. Reset Link Creation Create temporary reset links using Redirectly: ```javascript async function createPasswordResetLink(userId, token) { try { // Create temporary link that expires in 30 minutes const response = await redirectly.post('/v1/temp-links', { target: `https://redirectly.app/reset-password?token=${token}&user=${userId}`, ttlSeconds: 1800 // 30 minutes }); return response.data.url; } catch (error) { console.error('Failed to create reset link:', error); throw new Error('Failed to create reset link'); } } async function storeResetToken(userId, token, expiresAt) { // Store token in database with expiration await db.query( 'INSERT INTO password_reset_tokens (user_id, token, expires_at, used) VALUES (?, ?, ?, ?)', [userId, token, expiresAt, false] ); } ``` ### 3. Password Reset Handler Handle the actual password reset when the link is clicked: ```javascript app.post('/api/reset-password', async (req, res) => { try { const { token, userId, newPassword } = req.body; // 1. Validate token const tokenRecord = await db.query( 'SELECT * FROM password_reset_tokens WHERE token = ? AND user_id = ? AND expires_at > NOW() AND used = false', [token, userId] ); if (!tokenRecord.length) { return res.status(400).json({ error: 'Invalid or expired reset token' }); } // 2. Validate new password strength if (!isValidPassword(newPassword)) { return res.status(400).json({ error: 'Password does not meet requirements' }); } // 3. Hash new password const hashedPassword = await hashPassword(newPassword); // 4. Update user password await db.query( 'UPDATE users SET password = ? WHERE id = ?', [hashedPassword, userId] ); // 5. Mark token as used await db.query( 'UPDATE password_reset_tokens SET used = true WHERE token = ?', [token] ); // 6. Invalidate all user sessions (optional) await invalidateUserSessions(userId); res.json({ success: true, message: 'Password reset successfully' }); } catch (error) { console.error('Password reset error:', error); res.status(500).json({ error: 'Password reset failed' }); } }); ``` ## Flutter Integration ### 1. Password Reset Service Create a service to handle password reset functionality: ```dart class PasswordResetService { static final FlutterRedirectly _redirectly = FlutterRedirectly(); static Future initialize() async { await _redirectly.initialize(RedirectlyConfig( apiKey: 'YOUR_API_KEY', baseUrl: 'https://redirectly.app', enableDebugLogging: true, )); // Listen for incoming reset links _redirectly.onLinkClick.listen(_handlePasswordResetLink); // Check for initial link final initialLink = await _redirectly.getInitialLink(); if (initialLink != null) { _handlePasswordResetLink(initialLink); } } static void _handlePasswordResetLink(LinkClickEvent event) { if (event.error != null) { print('Link error: ${event.error}'); return; } final uri = Uri.parse(event.originalUrl); // Check if this is a password reset link if (uri.path.contains('/reset-password')) { final token = uri.queryParameters['token']; final userId = uri.queryParameters['user']; if (token != null && userId != null) { _navigateToPasswordReset(token, userId); } } } static void _navigateToPasswordReset(String token, String userId) { // Navigate to password reset screen with token Get.toNamed('/reset-password', arguments: { 'token': token, 'userId': userId, }); } static Future requestPasswordReset(String email) async { try { final response = await http.post( Uri.parse('${ApiConfig.baseUrl}/api/forgot-password'), headers: {'Content-Type': 'application/json'}, body: jsonEncode({'email': email}), ); return response.statusCode == 200; } catch (e) { print('Password reset request error: $e'); return false; } } static Future resetPassword(String token, String userId, String newPassword) async { try { final response = await http.post( Uri.parse('${ApiConfig.baseUrl}/api/reset-password'), headers: {'Content-Type': 'application/json'}, body: jsonEncode({ 'token': token, 'userId': userId, 'newPassword': newPassword, }), ); return response.statusCode == 200; } catch (e) { print('Password reset error: $e'); return false; } } } ``` ### 2. Password Reset UI Create the password reset screen: ```dart class PasswordResetScreen extends StatefulWidget { final String token; final String userId; const PasswordResetScreen({ Key? key, required this.token, required this.userId, }) : super(key: key); @override _PasswordResetScreenState createState() => _PasswordResetScreenState(); } class _PasswordResetScreenState extends State { final _formKey = GlobalKey(); final _passwordController = TextEditingController(); final _confirmPasswordController = TextEditingController(); bool _isLoading = false; @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('Reset Password'), backgroundColor: Colors.transparent, elevation: 0, ), body: Padding( padding: EdgeInsets.all(24.0), child: Form( key: _formKey, child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Text( 'Enter your new password', style: Theme.of(context).textTheme.headlineSmall, textAlign: TextAlign.center, ), SizedBox(height: 32), TextFormField( controller: _passwordController, obscureText: true, decoration: InputDecoration( labelText: 'New Password', border: OutlineInputBorder(), ), validator: (value) { if (value == null || value.isEmpty) { return 'Please enter a password'; } if (value.length < 8) { return 'Password must be at least 8 characters'; } return null; }, ), SizedBox(height: 16), TextFormField( controller: _confirmPasswordController, obscureText: true, decoration: InputDecoration( labelText: 'Confirm Password', border: OutlineInputBorder(), ), validator: (value) { if (value != _passwordController.text) { return 'Passwords do not match'; } return null; }, ), SizedBox(height: 32), ElevatedButton( onPressed: _isLoading ? null : _resetPassword, child: _isLoading ? CircularProgressIndicator(color: Colors.white) : Text('Reset Password'), ), ], ), ), ), ); } Future _resetPassword() async { if (!_formKey.currentState!.validate()) return; setState(() => _isLoading = true); final success = await PasswordResetService.resetPassword( widget.token, widget.userId, _passwordController.text, ); setState(() => _isLoading = false); if (success) { Get.snackbar( 'Success', 'Your password has been reset successfully!', backgroundColor: Colors.green, colorText: Colors.white, ); Get.offAllNamed('/login'); } else { Get.snackbar( 'Error', 'Failed to reset password. Please try again.', backgroundColor: Colors.red, colorText: Colors.white, ); } } } ``` ## Security Considerations **Critical Security Measures** - Use short expiration times (15-30 minutes) - Implement rate limiting (max 3 requests per hour per email) - Use cryptographically secure random tokens - Never reveal if email exists in system - Invalidate all user sessions after password reset - Log all password reset attempts for monitoring ### Rate Limiting - Limit reset requests per email/IP - Implement exponential backoff - Use Redis for distributed rate limiting - Monitor for abuse patterns ### Token Security - Generate 32+ byte random tokens - Store tokens with expiration - Mark tokens as used after reset - Clean up expired tokens regularly ## Testing & Validation ### Testing Checklist **Functional Testing** - Test reset request with valid email - Test reset request with invalid email - Verify email delivery - Test link expiration - Test password reset completion - Test token reuse prevention **Security Testing** - Test rate limiting - Test with expired tokens - Test with invalid tokens - Test session invalidation - Test password strength validation - Test HTTPS enforcement ## Secure Password Reset Implementation Complete! You now have a complete, secure password reset system that provides an excellent user experience while maintaining the highest security standards. Your users can reset their passwords seamlessly within your Flutter app. ## Related - [Flutter Deferred Deep Linking](https://redirectly.app/flutter-deferred-deep-linking.md) — Complete guide to implementing deferred deep linking in your Flutter apps. - [Email Verification with Deep Linking](https://redirectly.app/blog/email-verification-deep-linking.md) — Learn how to implement secure email verification using temporary deep links. --- Full site index for AI agents: https://redirectly.app/llms.txt