A password reset email is sent. When I access the link I have the possibility to change the password with another one. After typing the new password and pressing the button I receive the fallowing error:
The action code is invalid. This can happen if the code is malformed, expired, or has already been used.
This is my password_reset_page.dart:
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'dart:async';
final authProvider = Provider<FirebaseAuth>((ref) => FirebaseAuth.instance);
class PasswordResetPage extends ConsumerStatefulWidget {
const PasswordResetPage({super.key});
@override
PasswordResetPageState createState() => PasswordResetPageState();
}
class PasswordResetPageState extends ConsumerState<PasswordResetPage> {
final TextEditingController _emailController = TextEditingController();
bool _isLoading = false;
Timer? _debounce;
final GlobalKey<ScaffoldMessengerState> _scaffoldMessengerKey = GlobalKey<ScaffoldMessengerState>();
Future<void> _sendPasswordResetEmail() async {
setState(() {
_isLoading = true;
});
try {
final FirebaseAuth auth = ref.read(authProvider);
await auth.sendPasswordResetEmail(
email: _emailController.text,
actionCodeSettings: ActionCodeSettings(
url: 'https://romexpo-mobile-app.web.app/reset_password.html?oobCode=%CODE%&lang=ro',
handleCodeInApp: true,
),
);
_showDialog('Success', 'Password reset email sent. Please check your email.');
} on FirebaseAuthException catch (e) {
_showSnackbar(SnackBar(content: Text('Error: ${e.message}')));
} finally {
setState(() {
_isLoading = false;
});
}
}
void _onResetButtonPressed() {
if (_debounce?.isActive ?? false) return;
_debounce = Timer(const Duration(seconds: 1), () {
_sendPasswordResetEmail();
});
}
void _showSnackbar(SnackBar snackBar) {
_scaffoldMessengerKey.currentState?.hideCurrentSnackBar();
_scaffoldMessengerKey.currentState?.showSnackBar(snackBar);
}
void _showDialog(String title, String content) {
showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: Text(title),
content: Text(content),
actions: [
TextButton(
onPressed: () {
Navigator.of(context).pop();
},
child: const Text('OK'),
),
],
);
},
);
}
@override
Widget build(BuildContext context) {
WidgetsBinding.instance.addPostFrameCallback((_) {
_scaffoldMessengerKey.currentState?.hideCurrentSnackBar();
});
return ScaffoldMessenger(
key: _scaffoldMessengerKey,
child: Scaffold(
appBar: AppBar(title: const Text('Reset Password')),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
TextField(
controller: _emailController,
decoration: const InputDecoration(labelText: 'Email'),
),
const SizedBox(height: 20),
_isLoading
? const CircularProgressIndicator()
: ElevatedButton(
onPressed: _isLoading ? null : _onResetButtonPressed,
child: const Text('Reset Password'),
),
],
),
),
),
);
}
@override
void dispose() {
_debounce?.cancel();
_emailController.dispose();
super.dispose();
}
}
This is my reset_password.html
<!DOCTYPE html>
<html lang="ro">
<head>
<meta charset="UTF-8">
<title>Resetați parola</title>
<style>
.container {
max-width: 400px;
margin: auto;
padding: 20px;
border: 1px solid #ccc;
border-radius: 5px;
text-align: center;
}
input, button {
width: 100%;
padding: 10px;
margin: 10px 0;
}
#message {
margin-top: 20px;
}
</style>
</head>
<body>
<div class="container">
<h1>Resetați parola</h1>
<form id="reset-password-form">
<input type="password" id="new-password" placeholder="Noua parola" required>
<button type="submit">Resetați Parola</button>
</form>
<div id="message"></div>
</div>
<script src="https://www.gstatic.com/firebasejs/8.6.8/firebase-app.js"></script>
<script src="https://www.gstatic.com/firebasejs/8.6.8/firebase-auth.js"></script>
<script>
var firebaseConfig = {
apiKey: ".........",
authDomain: ".........",
projectId: "........",
storageBucket: ".........",
messagingSenderId: "..........",
appId: "........."
};
firebase.initializeApp(firebaseConfig);
var auth = firebase.auth();
document.addEventListener('DOMContentLoaded', function () {
var urlParams = new URLSearchParams(window.location.search);
var oobCode = urlParams.get('oobCode');
if (!oobCode) {
showMessage('Codul este invalid sau lipseste din URL.', 'red');
return;
}
document.getElementById('reset-password-form').addEventListener('submit', function (event) {
event.preventDefault();
var newPassword = document.getElementById('new-password').value;
auth.confirmPasswordReset(oobCode, newPassword)
.then(function () {
showMessage('Parola a fost resetată cu succes!', 'green');
})
.catch(function (error) {
handlePasswordResetError(error);
});
});
});
function showMessage(message, color) {
var messageDiv = document.getElementById('message');
messageDiv.style.color = color;
messageDiv.textContent = message;
}
function handlePasswordResetError(error) {
let errorMessage = 'A apărut o eroare: ';
switch (error.code) {
case 'auth/expired-action-code':
errorMessage += 'Codul a expirat. Vă rugăm să solicitați un alt e-mail pentru resetarea parolei.';
break;
case 'auth/invalid-action-code':
errorMessage += 'Codul este invalid. Vă rugăm să verificați linkul din e-mail sau să solicitați un alt e-mail pentru resetarea parolei.';
break;
case 'auth/user-disabled':
errorMessage += 'Acest cont a fost dezactivat.';
break;
case 'auth/user-not-found':
errorMessage += 'Nu există niciun utilizator asociat cu acest cod.';
break;
default:
errorMessage += error.message;
break;
}
showMessage(errorMessage, 'red');
}
</script>
</body>
</html>
And this is the link that I set up in the password reset email template in firebase: href=’https://romexpo-mobile-app.web.app/reset_password.html?oobCode=%CODE%&lang=ro’
Using this method I am trying to use an alternative to the firebase UI reset password that has only one rule : minimum 6 characters, also I am using this method to avoid using deep links.