I'm using Firebase Authentication and Firebase Cloud Firestore for my Flutter project. But the rules I implemented in the Firestore only apply correctly after restarting the app, if the user just registered.
Here is the code snippet for the relevant rules:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
function isSignedIn() {
return request.auth != null && request.auth.token.email_verified;
}
match /users/{userId}/{document=**} {
allow read, write: if isSignedIn() && request.auth.uid == userId;
}
}
}
This is how I register a new user:
Future<void> register({
required String email,
required String password,
String? name,
}) async {
try {
final credential = await FirebaseAuth.instance
.createUserWithEmailAndPassword(email: email, password: password);
await credential.user?.sendEmailVerification();
if (name?.isNotEmpty == true) {
await credential.user?.updateDisplayName(name);
}
if (user?.emailVerified == true) {
await loadPlayer(); // This adds the new object to the 'users' collection
}
} finally {
notifyListeners();
}
}
Future<void> loadPlayer() async {
final user = FirebaseAuth.instance.currentUser;
final userId = user?.uid;
if (userId != null && user?.emailVerified == true) {
var document = await FirebaseFirestore.instance
.collection('users')
.doc(userId)
.get();
if (document.exists == false) {
final name = user?.displayName ?? '';
await FirebaseFirestore.instance
.collection('users')
.doc(userId)
.set({'name': name});
document = await FirebaseFirestore.instance
.collection('users')
.doc(userId)
.get();
// After this I parse the document into my model class and continue with the app
}
}
}
If the email is not validated during the time of checking, the user is lead to a screen, where they can trigger the FirebaseAuth.instance.currentUser?.reload() to check again. If it comes back with an validated email, the app will once again try to add the user data to the users collection by calling the loadPlayer()-method.
But if I try this sequence with a new user, the first get()-call for the document in the loadPlayer()-method fails with a permission error:
[cloud_firestore/permission-denied] The caller does not have permission to execute the specified operation.
But if I restart the app and log in with the newly created user, all call succeed. Does anyone have an idea, why that might happen and how to resolve it?
2 Answers 2
Your Firestore rule depends on this condition: isSignedIn()
request.auth != null && request.auth.token.email_verified
email_verified is read from the user’s ID token, not directly from the Firebase Auth user object.
After a user verifies their email :
final user = FirebaseAuth.instance.currentUser;
await user?.reload();
if (user != null && user.emailVerified) {
await user.getIdToken(true); // force token refresh
await loadPlayer();
}
1 Comment
await user.getIdToken(true) worked!This code will not wait for the user to check their email and click it to verify their email address:
await credential.user?.sendEmailVerification()
All it does is arrange to send the verification email, then return when that email has been scheduled to be delivered, before the email is actually delivered. But your code goes on to assume that the email verification process is fully complete anyway. Also I will point out that the user object in this line of code is a different user object than the one that was returned by createUserWithEmailAndPassword:
if (user?.emailVerified == true) {
We can't see where you defined user, but it's not likely to be the same as credential.user. They're almost certainly different objects, unless there is something very special you're not showing here.
What you should do instead is make the new user wait until they validate their email (do not assume that their email address is immediately verified, do not perform any queries), then have them come back to your app to reload the user object to see if the flag was correctly set. Only then will your Firestore query use the user's credentials to satisfy the security rules.
Comments
Explore related questions
See similar questions with these tags.