I want to create new rules for my client - one client can create one document in collection.
match /Users/{userId} {
allow update, delete: if request.resource.data.uid == uid;
allow create: if
request.data.uid != request.resource.data.uid;
if request uid == request.resource.data.uid; he cannot.
If you want each user to only be able to create a single document, that is easiest if you use the user's UID as the document ID. In JavaScript that'd be something like this:
import { doc, setDoc } from "firebase/firestore";
import { getAuth } from "firebase/auth";
const user = getAuth().currentUser;
await setDoc(doc(db, "Users", user.uid), {
displayName: user.displayName,
});
You can then enforce this content-owner only access in security rules with:
service cloud.firestore {
match /databases/{database}/documents {
// Allow only authenticated content owners access
match /Users/{userId}/{documents=**} {
allow write: if request.auth != null && request.auth.uid == userId
}
}
}
The main difference with your approach is that we don't check the data of the document, but instead check the document ID (which we set to the UID in our code) to the UID on the request.
Related
My project has 2 main collections: "contact" and "albums".
Here is my data structure
I am trying to assign full read access to everyone in the "albums" collection and to restrict write access to unauthenticated users.
At the same time, i want to assign write access to unauthenticated users and read, update, delete to authenticated users in the "contact" collection.
The current ruleset fails in the rules simulator both for authenticated requests and unauthenticated requests. Rules are as follows:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /contact/{document=**} {
allow create;
allow read, update, delete: if request.auth != null;
}
}
match /albums/{document=**}{
allow read;
allow write: if request.auth != null;
}
}
And the firebase query returns "Uncaught Error in onSnapshot: FirebaseError: Missing or insufficient permissions."
Query below:
useEffect(() => {
const unmount = firestore
.collection("albums")
.orderBy("date", "asc")
.onSnapshot((snapshot) => {
const tempAlbums = [];
snapshot.forEach((doc) => {
tempAlbums.push({ ...doc.data(), id: doc.id });
});
setAlbums(tempAlbums);
});
return unmount;
}, []);
Any ideas on how to correct the rules?
There is a typo in your rules. It should be like this:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /contact/{document=**} {
allow create;
allow read, update, delete: if request.auth != null;
} //The /albums path must be included inside /documents
match /albums/{document=**}{
allow read;
allow write: if request.auth != null;
}
}
}
In my application, once the user is logged in via Firebase auth, I want to fetch additional data from my firestore database.
I do not want users to be able to look at other user's documents, therefore I created a rule for this:
match /users/{userId} {
function isAuthenticated() {
return request.auth != null;
}
function userIsSelf() {
return request.auth.uid == userId;
}
allow read: if
isAuthenticated()
&& userIsSelf();
}
In my head, what I wrote should in theory allow the current logged in user to see data only about himself, and this worked fine in the "rules playground".
However, when I try this code in in the app, I get an error: FirebaseError: Missing or insufficient permissions.
I think this has something to do with the way firestore fetches the data?
The way I query this is by fetching the collection users with a where that only returns users that has uid same as logged in user:
const querySnapshot = await firebase.firestore().collection("users").where("uid", "==", uid).get().catch(err => {
console.error('could not fetch user', err)
})
if (!querySnapshot || querySnapshot.empty) {
dispatch('logout')
throw new Error('Cannot find logged in user\'s data in database')
}
const userData = querySnapshot.docs[0].data()
commit('setUser', userData)
I created my firestore rules to look like this:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
function isAuthenticated() {
return request.auth != null;
}
function isAdmin(uid) {
return get(/databases/$(database)/documents/users/$(uid)).data.role == 'admin'
}
// Allow admin to do everything
match /{document=**} {
allow read, write: if isAdmin(request.auth.uid)
}
// Only allow users to read/write to themselfs
match /users/{userId} {
function userIsSelf() {
return request.auth.uid == userId;
}
function roleIsUserOrSameAsDocument() {
return request.resource.data.role == 'user' || request.resource.data.role == resource.data.role;
}
allow read: if
isAuthenticated()
&& userIsSelf();
allow write: if
isAuthenticated()
&& userIsSelf()
&& roleIsUserOrSameAsDocument();
}
}
}
Your query doesn't match your rules. Your query is attempting to get all documents where the uid field matches the provided uid:
firebase.firestore()
.collection("users")
.where("uid", "==", uid)
But your rules are saying that users may only access the individual document with the ID that matches their UID. The rule is going to reject this query every time, because it's not looking at the document ID at all, just a field.
Your rules allow this query instead:
firebase.firestore()
.collection("users")
.doc(uid)
If you actually do want to allow the user to access any document where their UID matches the uid field in the document, you will need to adjust them like this:
match /users/{userId} {
function isAuthenticated() {
return request.auth != null;
}
function checkDocUid() {
return request.resource.data.uid == request.auth.uid;
}
allow read: if isAuthenticated() && checkDocUid();
}
Note that request.auth.uid is the currenetly auth'd user's uid and request.resource.data.uid is the value of the uid field in the document.
Always remember that your query must match the rules exactly, and that security rules are not filters.
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
I cannot get this firestore rule to work.
I want to write/read to user-read-only/USER-ID-HERE/business/settings
service cloud.firestore {
match /databases/{database}/documents {
match /user-read-only/{userId} {
allow read, update, delete: if request.auth.uid == userId;
allow create: if request.auth.uid != null;
match /{document=**} {
allow read, update, delete: if request.auth.uid == userId;
allow create: if request.auth.uid != null;
}
}
}
}
I continue to get the message
FirebaseError: Missing or insufficient permissions.
I have tried many different approaches with the simulator and they are all successful, but I can’t repro from my app.
Does anything look incorrect above?
Can the above be simplified? I would like the user to be able to control everything beyond {userId}
How do I know if request.auth.uid and userId are populating properly?
This works
service cloud.firestore {
match /databases/{database}/documents {
match /{userId}/{document=**} {
allow read, write;
}
}
}
This does not work
service cloud.firestore {
match /databases/{database}/documents {
match /{userId}/{document=**} {
allow read, write: if request.auth.uid == userId;
}
}
}
Update following your comment "The intent is to expand the rule so that anything beyond {userId} can be managed by the user":
service cloud.firestore {
match /databases/{database}/documents {
match /user-read-only/{userId}/{document=**} {
allow read, update, delete: if request.auth.uid == userId;
allow create: if request.auth.uid != null;
}
}
}
Just note that the create rule (copied from your question) allows any authenticated user to write under any {userId} folder.
(On the opposite if you just want to declare a rule for business/settings sub-collection and doc) the following should do the trick:
service cloud.firestore {
match /databases/{database}/documents {
match /user-read-only/{userId}/business/settings {
allow read, update, delete: if request.auth.uid == userId;
allow create: if request.auth.uid != null;
}
}
}
In order to be sure that userId is populated properly, you could add it as a field to the document when created and check in the rules for create that it is correct, as follows:
allow create: if request.auth.uid != null && request.auth.uid == request.resource.data.userId;
On the other hand, Firebase Auth will automatically ensure that request.auth.uid is correctly populated.
Finally, you may watch this very good video from the Firebase team about Security Rules : https://www.youtube.com/watch?v=eW5MdE3ZcAw
Here is the HTML page used for testing. Just change the value of userId with the different user's ID.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Title</title>
<script src="https://www.gstatic.com/firebasejs/5.9.3/firebase.js"></script>
<script>
// Initialize Firebase
var config = {
apiKey: 'xxxxx',
authDomain: 'xxxxx',
databaseURL: 'xxxxx',
projectId: 'xxxxx'
};
firebase.initializeApp(config);
firebase
.auth()
.signInWithEmailAndPassword('xxxxxx#gmail.com', 'yyyyyyy')
.then(userCredential => {
const userId = userCredential.user.uid;
// Replace with another userId to test
//e.g. const userId = 'l5Wk7UQGRCkdu1OILxHG6MksUUn2';
firebase
.firestore()
.doc('user-read-only/' + userId + '/business/settings4')
.set({ tempo: 'aaaaaaa' })
.then(() => {
return firebase
.firestore()
.doc(
'user-read-only/' + userId + '/testC/1/collec/2'
)
.get();
})
.then(function(doc) {
if (doc.exists) {
console.log('Document data:', doc.data());
} else {
// doc.data() will be undefined in this case
console.log('No such document!');
}
})
.catch(function(error) {
console.log('Error getting document:', error);
});
});
</script>
</head>
<body>
</body>
</html>
Did you deploy security rules?
See: https://firebase.google.com/docs/firestore/security/get-started#deploying_rules
Before you can start using Cloud Firestore from your mobile app, you will need to deploy security rules. You can deploy rules in the Firebase console or using the Firebase CLI.
Did you have loggedin using Firebase Authentication?
See: https://firebase.google.com/docs/firestore/security/rules-conditions
If your app uses Firebase Authentication, the request.auth variable contains the authentication information for the client requesting data. For more information about request.auth, see the reference documentation.
How do you call Firestore method?
See:
https://firebase.google.com/docs/firestore/data-model
https://firebase.google.com/docs/reference/js/firebase.auth.Auth#currentuser
https://firebase.google.com/docs/reference/js/firebase.User
Like this?
var userId = firebase.auth().currentUser.uid
var docRef = db.doc(`user-read-only/${userId}/business/settings`);
docRef.get().then(function(doc) {
if (doc.exists) {
console.log("Document data:", doc.data());
} else {
console.log("No such document!");
}
}).catch(function(error) {
console.log("Error getting document:", error);
});
I think you should change structure data.
A structure data should be like db.collection('coll').doc('doc').collection('subcoll').doc('subdoc').
(Collections->doc->SubCollections->SubDoc->SubSubCollections->SubSubDoc)
So {userId} should be docId. Not collections.
The security rules should be the this.
match /databases/{database}/documents {
match /users/{userId} {
allow read, update, delete: if request.auth.uid == userId;
allow create: if request.auth.uid != null;
match /settings/{setting} {
allow read, update, delete: if request.auth.uid == userId;
allow create: if request.auth.uid != null;
}
}
}
The settings collection ref is db.collection('users').doc(userId).collection('settings').
If does not work then you should try basic rule sets.
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write: if request.auth.uid != null;
}
}
}
Using flutter, I have installed the firebase-auth and firestore packages and am able to both authenticate with firebase auth and make a call into firestore as long as I don't have any rules around the user.
I have a button that calls _handleEmailSignIn and I do get a valid user back (since they are in the Firebase Auth DB)
import 'package:firebase_auth/firebase_auth.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
final FirebaseAuth _auth = FirebaseAuth.instance;
void _handleEmailSignIn(String email, String password) async {
try {
FirebaseUser user = await _auth.signInWithEmailAndPassword(
email: email, password: password);
print("Email Signed in " + user.uid); // THIS works
} catch (err) {
print("ERROR CAUGHT: " + err.toString());
}
}
I then have another button that calls this function to attempt to add a record into the testing123 collection.
Future<Null> _helloWorld() async {
try {
await Firestore.instance
.collection('testing123')
.document()
.setData(<String, String>{'message': 'Hello world!'});
print('_initRecord2 DONE');
} catch (err) {
print("ERROR CAUGHT: " + err.toString());
}
}
Now this works as long as I don't have any rules around checking the request user. This works...
service cloud.firestore {
match /databases/{database}/documents {
match /testing123auth/{doc} {
allow read, create
}
}
}
This does not which gives PERMISSION_DENIED: Missing or insufficient permissions. when I want to make sure I have the authenticated user I did with _handleEmailSignIn.
service cloud.firestore {
match /databases/{database}/documents {
match /testing123auth/{doc} {
allow read, create: if request.auth != null;
}
}
}
I suspect that the firestore request is not including the firebase user. Am I meant to configure firestore to include the user or is this supposed to be automatic as part of firebase?
One thing to note that's not well documented is that firebase_core is the "Glue" that connects all the services together and when you're using Firebase Authentication and other Firebase services, you need to make sure you're getting instances from the same firebase core app configuration.
final FirebaseAuth _auth = FirebaseAuth.instance;
This way above should not be used if you're using multiple firebase services.
Instead, you should always get FirebaseAuth from FirebaseAuth.fromApp(app) and use this same configuration to get all other Firebase services.
FirebaseApp app = await FirebaseApp.configure(
name: 'MyProject',
options: FirebaseOptions(
googleAppID: Platform.isAndroid ? 'x:xxxxxxxxxxxx:android:xxxxxxxxx' : 'x:xxxxxxxxxxxxxx:ios:xxxxxxxxxxx',
gcmSenderID: 'xxxxxxxxxxxxx',
apiKey: 'xxxxxxxxxxxxxxxxxxxxxxx',
projectID: 'project-id',
bundleID: 'project-bundle',
),
);
FirebaseAuth _auth = FirebaseAuth.fromApp(app);
Firestore _firestore = Firestore(app: app);
FirebaseStorage _storage = FirebaseStorage(app: app, storageBucket: 'gs://myproject.appspot.com');
This insures that all services are using the same app configuration and Firestore will receive authentication data.
There shouldn't be any special configuration needed for the firestore to do this.
This is all you should need.
Modified from Basic Security Rules:
service cloud.firestore {
match /databases/{database}/documents {
match /testing123/{document=**} {
allow read, write: if request.auth.uid != null;
}
}
}
It seems they check if the uid is null rather than the auth itself. Try this out and see if it works. Also, it seemed that your code was inconsistent as the firestore rule had testing123auth and flutter had testing123. I'm not sure if that was intentional.
to check if the user is signed in you should use
request.auth.uid != null
I would have suggested to make the rule like:
service cloud.firestore {
match /databases/{database}/documents {
match /testing123auth/{documents=**} {
allow read, create: if true;
}
}
}
Or, better yet, limit the scope of the user:
service cloud.firestore {
match /databases/{database}/documents {
match /testing123auth/{userId} {
allow read, create:
if (request.auth.uid != null &&
request.auth.uid == userId); // DOCUMENT ID == USERID
} // END RULES FOR USERID DOC
// IF YOU PLAN TO PUT SUBCOLLECTIONS INSIDE DOCUMENT:
match /{documents=**} {
// ALL DOCUMENTS/COLLECTIONS INSIDE THE DOCUMENT
allow read, write:
if (request.auth.uid != null &&
request.auth.uid == userId);
} // END DOCUMENTS=**
} // END USERID DOCUMENT
}
}