addPost = async ({ text, localUri }) => {
const remoteUri = await this.uploadPhotoAsync(localUri);
return new Promise((res, rej) => {
this.firestore
.collection("posts")
.add({
text,
uid: this.uid,
timestamp: this.timestamp,
image: remoteUri
})
.then(ref => {
res(ref);
})
.catch(error => {
rej(error);
});
});
};
And
rules_version = '2';
service firebase.storage {
match /posts/{doc} {
allow read, write: if true;
}
}
Above is my react-native code and below it is the firebase rules for my database so far. Still getting
FirebaseError:[code=permission-denied]: Missing or insufficient permissions.
Any help as to how to fix this code or to make my rules more secure? At this point in the code the user has been authenticated.
The rules in the question are for firebase storage, you need to change the rule for firestore to true:
// Allow read/write access to all users under any conditions
// Warning: **NEVER** use this rule set in production; it allows
// anyone to overwrite your entire database.
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write: if true;
}
}
}
Check here for more information:
https://firebase.google.com/docs/firestore/security/insecure-rules
Related
What I'm trying to achieve is to only show the documents that match request.auth.uid. I've seen these examples on a lot of website but none of them seems to work for me. No matter what article I read I see these examples there but none of them seems to work for me. I have a (posts) collection with bunch of documents with auto-generated (ids).
// I cannot attach more than 2 images it gives me formatting error that's why.
https://i.stack.imgur.com/M84P5.png
https://i.stack.imgur.com/hbBii.png
https://i.stack.imgur.com/lm4HH.png
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /posts/{postId} {
allow read: if request.auth.uid == resource.data.userId; // This doesn't work
}
}
}
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /posts/{postId} {
allow read: if request.auth.uid == postId; // This also doesn't work
}
}
}
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /posts/{postId} {
allow read: if request.auth.uid != null; // even though this works fine but it is general.
}
}
}
Below I've shared my code.
class App extends React.Component {
constructor() {
super();
this.state = { social: [], loggedIn: "false" };
}
componentDidMount = () => {
firestore.collection("posts").onSnapshot((snapshot) => {
const social = snapshot.docs.map((doc) => {
return { id: doc.id, ...doc.data() };
});
this.setState({
social
});
});
};
// clickHandle(id, likes) {
// firestore
// .collection("posts")
// .doc(id)
// .update({ likes: likes + 1 });
// }
handleCreate() {
var obj = { title: "Never Ever GIve Up" };
firestore.collection("posts").add(obj);
}
handleDelete(id) {
firestore.collection("posts").doc(id).delete();
}
handleLogin() {
var provider = new firebase.auth.GoogleAuthProvider();
auth.signInWithPopup(provider).then((result) => {
console.log(result);
});
}
handleLogout() {
firebase.auth().onAuthStateChanged((user) => {
auth.signOut();
});
}
render() {
return (
<div>
<button onClick={this.handleLogin.bind(this)}>Login to Google</button>
<button onClick={this.handleLogout.bind(this)}> Logout from Google</button>
{this.state.social.map((obj) => {
return (
<h1>{obj.title} </h1>
);
})}
<br />
<button id="create" onClick={this.handleCreate.bind(this)}>
Create
</button>
</div>
);
}
}
ReactDOM.render(<App />, document.querySelector("#root"));
The problem is that Firestore security rules are not filters. Please read that documentation carefully, and also this blog.
Your query is asking for all documents in the posts collection. However, your rules do not allow that. The rules will not filter the documents that match the rules. Instead, your client app needs to filter for the documents that the user should be able to read, and your rules need to validate that filter.
If you want to require the userId field to be the same as the authenticated user ID for the purpose of reading the document, your query needs to add a filter to ensure they aren't asking for anything more than they have permission to read:
firestore.collection("posts").where("userId", "==", uid)
Where uid is the user ID that you got from the Firebase Auth SDK.
Then your rules can check to see that the filter is correct:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /posts/{postId} {
allow read: if request.auth.uid == resource.data.userId;
}
}
}
I bet you're doing something funky with your requests.
resource.data will return the attributes of the document accessed. In your first example, this will work if the document at path posts/{post_id} has the attribute userId, and the userId attribute matches the uid of the incoming request context. I would verify the structure of each document in the posts collection to check that it matches.
The first example should work, I would double-check the user id matches of the sender. Check the "Authentication" tab of your Firebase console. (In the document shown, looks like the userId is the id of another post, which might indicate some misbehavior?)
The second example won't work because the document doesn't have a postId attribute.
I set up Firebase Functions to make calls to my Firestore. I'm using admin.auth() and returning data. I set up custom rules in the Firestore Rules section, but the Functions are not following the Rules i.e. when I use the URL in Postman, I shouldn't be getting the data because it doesn't fulfill "if read, write: if request.auth != null". How do I address this?
Here is my Firebase Function Code:
const admin = require('firebase-admin');
module.exports = function(req, res) {
const uid = req.body.uid;
admin
.auth()
.getUser(uid)
.then(user => {
admin
.firestore()
.collection('discover')
.get()
.then(snapshot => {
res.send(
snapshot.docs.map(doc => {
const data = Object.assign({ doc_id: doc.id }, doc.data());
return data;
})
);
})
.catch(err => {
res.send({ message: 'Something went wrong!', success: false });
});
})
.catch(err => {
res.send({ error: 'Something went wrong!', success: false });
});
};
Firestore Rules:
service cloud.firestore {
match /databases/{database}/documents {
match /users/{users} {
allow read: if request.auth != null;
}
match /discover/{discover} {
allow read: if request.auth != null;
}
match /favorites/{favorite} {
allow read, write: if request.auth != null;
}
}
}
I shouldn't be able to get this data from Postman (Since I'm not authenticated) but I'm still getting the data. I don't want the data to be accessible if the user is not logged in.
Security rules don't apply when you access the database via the Admin SDK, or any other time you use a service account. It doesn't matter at all that you're using postman (or any other HTTP client). The thing actually doing the database access here is the Admin SDK.
I'm using vuex and firebase to implement user authentication following the instruction of vuegram. I tried many ways to detach firebase listeners, the only one that stop warning error is the following:
var unsubscribe=fb.auth.onAuthStateChanged(user=>{
if(user){
store.commit('setCurrentUser',user)
store.dispatch('fetchUserProfile')
fb.usersCollection.doc(user.uid).onSnapshot(doc => {
store.commit('setUserProfile', doc.data())
})
}
})
unsubscribe();
However, the code above just stop warning on signOut(), I can't update data anymore.
My store.js file:
var unsubscribe=fb.auth.onAuthStateChanged(user=>{
if(user){
store.commit('setCurrentUser',user)
store.dispatch('fetchUserProfile')
fb.usersCollection.doc(user.uid).onSnapshot(doc => {
store.commit('setUserProfile', doc.data())
})
}
})
export const store=new Vuex.Store({
state:{
currentUser:null,
userProfile:{}
},
actions:{
clearData({commit}){
commit('setCurrentUser',null)
commit('setUserProfile', {})
},
fetchUserProfile({ commit, state }) {
fb.usersCollection.doc(state.currentUser.uid).get().then(res => {
commit('setUserProfile', res.data())
}).catch(err => {
console.log(err)
})
},
updateProfile({ commit, state }, data) {
let displayName = data.displayName
fb.usersCollection.doc(state.currentUser.uid).set({
displayName: displayName
}, {merge:true}).then(function() {
alert("Document successfully written!");
})
.catch(function(error) {
alert("Error writing document: ", error);
});
}
},
mutations:{
setCurrentUser(state, val) {
state.currentUser = val
},
setUserProfile(state, val) {
state.userProfile = val
}
}
})
The signOut method:
signOut: function(){
fb.auth.signOut().then(()=> {
this.$store.dispatch('clearData')
this.$router.push('login')
}).catch(function(error) {
console.log(error);
});
}
My firebase rule:
allow read, write: if request.auth.uid!=null;
Since you still have an active listener when you log out, the system detects that the client has lost permission to read that data and rejects the listener. This means you need to remove the listener before signing out to prevent the error message.
See the documentation on detaching listeners, you first get a reference to the unsubscribe function when you attach the listener:
unsubscribe = fb.usersCollection.doc(user.uid).onSnapshot(doc => {
store.commit('setUserProfile', doc.data())
})
And then call that function before signing out:
signOut: function(){
unsubscribe();
fb.auth.signOut().then(()=> {
this.$store.dispatch('clearData')
this.$router.push('login')
}).catch(function(error) {
console.log(error);
});
}
If you have a logout button on many pages just navigate to a different page and call logout from there. Then you don't have to litter your code with unsubscribes.
You can also check your Firestore rules.
If this is what is there:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write: if false;
}
}
}
Change to:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write: if true;
}
}
}
Please note that this is insecure and for dev purposes only. To understand how Firestore rules work, check out this youtube video.
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
}
}
firebase.initializeApp(config);
const db = firebase.firestore();
const googleSignIn = async () => {
return await Expo.Google.logInAsync({
androidClientId,
iosClientId,
scopes: ['profile', 'email'],
});
};
const firebaseLogin = async (accessToken) => {
const cred = firebase.auth.GoogleAuthProvider.credential(null, accessToken);
await firebase.auth().signInWithCredential(cred).catch(console.error);
const idToken = await firebase.auth().currentUser.getIdToken(true).catch(console.error);
};
await firebaseLogin(googleSignIn().accessToken);
db.collection("any").doc().set({test: "OK"})
I get a permission denied error when trying to write to Firestore using a request.auth.uid != null; security rule, but when I replace it with true it works.
It seems that the Firestore component of the web SDK does not send authentication details, even though the API on the client reports Firebase is logged in, and the user last login date appears in the web GUI.
Do I need to pass authentication details to the Firestore component when logging in directly with Google (instead of using the Firebase login APIs)?
The code is running in a React Native app via Expo.
Another example that gets a permission denied:
firebase.auth().onAuthStateChanged((user) => {
if (user) {
firebase.firestore().collection("any").doc().set({test: "OK"});
}
});
Rules
// This is OK:
service cloud.firestore {
match /databases/{database}/documents {
match /any/{doc} {
allow read, write: if true;
}
}
}
// Permission denied
service cloud.firestore {
match /databases/{database}/documents {
match /any/{doc} {
allow read, write: if request.auth.uid != null;
}
}
}
Related
Firebase Firestore missing or insufficient permissions using Expo (React Native)
https://forums.expo.io/t/firestore-with-firebase-auth-permissions-to-read-write-only-to-signed-in-users/5705
This solution, and possibly this whole issue, may be specific to React Native.
In a similar question, Jade Koskela noticed that requests were missing the Origin header, and applies a patch to React's XHRInterceptor to work around the missing auth object:
const xhrInterceptor = require('react-native/Libraries/Network/XHRInterceptor');
xhrInterceptor.setSendCallback((data, xhr) => {
if(xhr._method === 'POST') {
// WHATWG specifies opaque origin as anything other than a uri tuple. It serializes to the string 'null'.
// https://html.spec.whatwg.org/multipage/origin.html
xhr.setRequestHeader('Origin', 'null');
}
});
xhrInterceptor.enableInterception();