How to set Firestore security rules for create operation - firebase

I am working on developing a mobile app in Flutter and using Firebase as backend.
Users will need to sign up to be able to use my app and I will save some user data in a user_profile collection in Firestore.
Since a new document will be added to the user_profile collection, I wonder how security rules should be set for creating a new document for the first time when the user registers.
Here is what happens during the sign up in my case:
// Step 1: Register user with email and password
FirebaseAuth _auth = FirebaseAuth.instance;
await _auth.createUserWithEmailAndPassword(email: email, password: password);
// Step 2: save user data in user_profile collection
FirebaseFirestore.instance
.collection('user_profile')
.doc(user.uid)
.set({
'uid': user.uid,
'email': email,
'name': name,
'date': DateTime.now,
})
Since I save the user data in the user_profile collection after the call for createUserWithEmailAndPassword(..) function is completed (meaning that the user is now authenticated), is it safe to define the security rule for create operation in Firebase as following? Or should I set it differently to make it secure in the right way?
// Rules for User Profile data
match /user_profile/{any} {
// Applies to writes to non-existent documents
allow create: if request.auth != null;
}

From my experience, this should be fine but you could additionally check that the uid contained in the document corresponds to the authenticated user.
allow create: if request.auth != null &&
request.auth.uid == request.resource.data.uid;

Related

Firebase Document Fields are getting Deleted

For some time now, I have noticed that the fields of some of my firebase documents get deleted, even though I do not delete them or write any logic on my app to delete fields of a document or a document itself. This is a picture that shows a document on my firebase project, which has its fields deleted on its own.
The firebase project is connected to my flutter app, but I have not written any delete functionality.
I only update the fields occasionally with recent data.
Please, who has any idea of what's happening?
Edit
This is how I update the documents of my user collections
Future<String?> submitUpdate(User user) async{
CollectionReference userCollection = firestore.collection('users');
String? uid = await secureStorage.read(key: 'uid');
try{
//updates user doc in users collection
await userCollection.doc(uid).update(user.toMap());
return "Success";
}catch(e){
return null;
}
This is how i create the user document in the users collection.
Future<String?> saveUserCredentials(User user) async{
CollectionReference users = firestore.collection('users');
String? uid = await FlutterSecureStorage().read(key: "uid");
try{
//creates a user doc in the users collection
await users.doc(uid).set(
user.toMap()
);
return "Success";
}catch (e){
print(e);
return null;
}
I have a User model that defines the user object.
If you want to set some fields of a document, but keep existing fields, use set with SetOptions(merge: true):
FirebaseFirestore.instance
.collection(...)
.doc(...)
.set({'field1': value1, 'field2': value2}, SetOptions(merge: true));
Without this, the existing fields that are not listed in set will be not be kept, probably this happened to you.

How to make Flutter authentification just over Firestore (without Firebase Auth)

I have to create a manual Registration and Sign in over Firestore without using Firebase Auth.
The reason for that requrirement is once when user wants to reset his password, first of all I have to sent him a confirmation code (MD5) on his email address and when he receives the code, above new password that he should enter, he must paste that code.
I'll sent him a code on email and in the same time, that code will be also in Firestore from which later on I will compare (code is correct or not).
By the end, (if entered code is correct, his new password will overwrite previous stored password in his document). Documents are named by email address which means that email should be unique.
Here is the code that I'm using for registration:
void onPressedRegister(BuildContext context, String fullName, String email,
String password, String phoneNumber, dynamic formKey) {
if (formKey.currentState.validate()) {
db.collection("firestoreUsers").document(email).setData({
'fullName': fullName,
'email': email,
'password': password,
'phoneNumber': phoneNumber
});
Now I have a problem with Sign in because I can't check is user stored in database or not. Here is what I wrote:
Future<bool> signInOverFirestore(String email, String password) async {
db
.collection('firestoreUsers')
.where('email', isEqualTo: email)
.getDocuments();
return true;
}
So is it possible to make sign in like this and also update that user later on when he enter correct code from his email, or not?
If you're not using Firebase Auth with Firestore and security rules, it's basically impossible to have secure per-user data. What you have now is storing plaintext email addresses and passwords for everyone in the world to see. All someone has to do is use the Firestore REST API to query the entire firestoreUsers collection to see everything.
According to my understanding, you want to make Firestore data public (access/read without authentication) check if some document with a value and if that value doesn't exist, you want to create a new document. In order to do that you can try to fetch the document which is equal to your value(email) and catch the error or listen if it is successful. You may accomplish it with the following example.
_db
.collection('path')
.where('field', isEqualTo: '')
.getDocuments()
.then((value) {
print('User on Firestore : $value');
}).catchError((e) {
print('Error no document found');
});

Flutter, Firebase Auth How to access uid everywhere in the app?

it's my first app I try to code.
I am using Firebase Auth and Cloud Firestore in my app. How can I make the uid of the logged-in User available on every screen without using await?
I know I can get it that way:
final FirebaseUser user = await _auth.currentUser();
String id = user.uid;
but I need to access the uid without awaiting it, for example here:
Stream<QuerySnapshot> get groups {
return Firestore.instance.collection("userGroups")
.where("userId", isEqualTo: " uid ").snapshots();
}
As I am using the provider package I thought I can make it available through it. But how is the best way to do it?
Thank You!
You can provide the firebase auth stream above your app:
StreamProvider<FirebaseUser>.value(
value: FirebaseAuth.instance.onAuthStateChanged,
lazy: false,
child: YourApp(),
)
and when want to get the user you can:
final user = Provider.of<FirebaseUser>(context);
Note, this will not listen to changes to the user object beyond signins and sign outs. To listen to actual user changes on the user (such as isVerified or photoUrl changes) you'll want to use a package like firebase_user_stream and replace the previous stream with something like:
StreamProvider<FirebaseUser>.value(
value: FirebaseUserReloader.onAuthStateChangedOrReloaded.asBroadcastStream(),
lazy: false,
),

Firestore permissions inconsistent when user registers

I have Firebase auth set up with email & password, and after the user creates an account, I create a document for the user in Firestore.
It usually works, but sometimes (maybe 10%) the user is created in Auth but not in firestore, with the error code:
FirebaseError: [code=permission-denied]: Missing or insufficient permissions
function registerUser (name, email, password){
firebase.auth().createUserWithEmailAndPassword(email, password).then(function() {
// Registration successful, create firestore document
db.collection("users").doc(email).set({
name: name,
email: email,
level: maxProblemSet,
premium: false
})
.then(function() {
//Success
window.location.replace(myUrl);
}).catch(function(error){
//Registered with auth but not stored in database
alert(error);
});
}).catch(function(error) {
// Handle Auth Errors here.
alert(error);
});
}
My security rules for the users collection look like this:
match /users/{user} {
allow read, write: if request.auth.token.email == user;
}
Is it possible that you are logged in as a different user?
One possible way to debug this is by running the following in the Javascript console in myURL to verify the user identity as seen by Firebase:
firebase.auth().currentUser
If this works as expected, you could try to print the doc in the console to see if the doc is now visible:
db.collection("users").doc(email).get().then(function(doc) { console.log(doc.data()); })

Flutter - Your Cloud Firestore database has insecure rules

I have a collection called users where I am checking if new users mobile no is present or not. If It is present then I am performing phone authentication for that user then storing uid as a field in document.
If user is coming for the first time, he is not authenticated and I am performing read operation from users collection. Now every time I am getting Your Cloud Firestore database has insecure rules email from google.
Below is the rule I am using. Please let me know how can I make it secure.
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read: if true;
allow write: if request.auth != null;
}
}
}
You can change your rule adding more security like this:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write: if request.auth != null;
}
}
}
But, then your app won't be able to read from Firebase, since you are telling that even for read is necessary to be authenticated.
I solved this allowing users to authenticate anonymously in Firebase. For this go to:
https://console.firebase.google.com/project/[YOUR-PROJECT]/authentication/providers
and enable Anonymous method. Remember to change [YOUR-PROJECT] in the URL.
After this you will only need to add few lines of code in your main screen or whatever you want.
1) Import the Firebase Auth package:
import 'package:firebase_auth/firebase_auth.dart';
2) Add the following code at the beginning of your main StatefulWidget:
final FirebaseAuth firebaseAuth = FirebaseAuth.instance;
Future<FirebaseUser> signInAnon() async {
AuthResult result = await firebaseAuth.signInAnonymously();
FirebaseUser user = result.user;
print("Signed in: ${user.uid}");
return user;
}
void signOut() {
firebaseAuth.signOut();
print('Signed Out!');
}
3) And now you just have to call the function inside your initState:
signInAnon().then((FirebaseUser user){
print('Login success!');
print('UID: ' + user.uid);
});
And voilá! Now every user user will authenticate anonymously automatically in your Firebase database. The best part is the user persists in the app until you uninstall it or delete the cache data.
Here is a video explaining the steps, but using a login screen which I removed for my project and this example: https://www.youtube.com/watch?v=JYCNvWKF7vw

Resources