问题
I have carefully looked at other answers to similar questions and as far as I can tell everything is set correctly but still access is denied.
Minimum working example:
My Firebase Live Database Security Rule for the path 'user/{uid}' is as follows
"rules": {
"user": {
"$uid": {
".read": "auth.uid === $uid",
".write": "auth.uid === $uid && !data.exists() "
}
}
}
in the typescript I attempt to read 'user/{uid}' for some user.
//Firebase and firebase-fcuntions import
const firebase = require("firebase");
const functions = require('firebase-functions');
//Reference to root of database
const rootdb = firebase.database();
//Read data
function foo(data, context){
return rootdb.ref('user').child(context.auth.uid).once('value')
.then(snap => //do stuff)
.catch( err => { console.log("Unsuccessful.")})
}
//Make call available from application using authentication
exports.enable_foo = functions.https.onCall( (data, context) => foo(data, context) );
The logs on firebase display:
Error: permission_denied at /user/XCRR0JK3xxZMoyoKzTIeQ2n1HcY2: Client doesn't have permission to access the desired data. a...
and the "Unsuccesful" message for the catch path of execution prints.
What am I missing?
Edit 1
I should mention that the actual method, as opposed to the minimum working example above, does check for the user being logged in and prints the user auth.uid so I can confirm the user is logged in.
//Firebase and firebase-fcuntions import
const firebase = require("firebase");
const functions = require('firebase-functions');
//Reference to root of database
const rootdb = firebase.database();
//Read data
function foo(data, context){
// Checking that the user is authenticated.
if (!context.auth) {
console.log("No authentication.")
throw new functions.https.HttpsError('Authentication', "You are not authorized to execute this function." );
}
console.log( context.auth.uid )
return rootdb.ref('user').child(context.auth.uid).once('value')
.then(snap => //do stuff)
.catch( err => { console.log("Unsuccessful.")})
}
//Make call available from application using authentication
exports.enable_foo = functions.https.onCall( (data, context) => foo(data, context) );
When I execute this function the {uid} of the user shows up in the logs when I print it.
Edit 2
Replacing the 'firebase' requirement by "firebase-admin" appears to "fix" the issue, that is to say it allows the read.
I have a security concern with this, namely that users who are authenticated and DO have access to a resource are denied said resource if I use the "firebase" requirement. Needing the full access "firebase-admin" to allow a user to access(read or write) a resource seems over kill and unintended.
So, I suppose the question now is: is this intended behaviour?
回答1:
When your client app invokes a callable function, the identity of the authenticated user is passed along to the function, but that doesn't mean the function is somehow automatically initializing any other modules with the user's credentials. So, if you import the Firebase client SDK, it is effectively running in unauthenticated mode (if it works at all - it was meant to run in browser environments, not nodejs).
Since backend code runs in a privileged environment that the user can't modify, it's typically said to be safe to run any code, since you wrote it, and you know exactly what it does. That's why it's recommended to user firebase-admin for access, which is meant primarily for backends and not client apps.
Now, if your concern is that the user might trigger the function and try to make it do something on their behalf that they shouldn't be able to do, you will have to write some code for that. The typical choice is to validate that the UID is allowed to do what the function is going to do. This means duplicating the checks of any security rules that would normally be used to protect the database.
Your other choice, which is going to suffer in performance, is to use the databaseAuthVariableOverride to tell the Admin SDK that it should use a different UID to access Realtime Database. Then it will respect all security rules. The problem here is that you have to init and then delete the firebase-admin instance for each request so that you don't leak memory. I show an example of this in another answer that uses an HTTP function to receive an auth ID token, validate its UID, and use the UID to init the SDK. Since you are using a callable type function, the validation has already been done for you, so all you have to do is use the given UID from the function in the same way.
回答2:
According to the Firebase documentation
These rules grant access to a node matching the authenticated user's ID from the Firebase auth token.
{
"rules": {
"users": {
"$uid": {
".read": "$uid === auth.uid",
".write": "$uid === auth.uid"
}
}
}
}
These rules give anyone, even people who are not users of your app, read and write access to your database.
{
"rules": {
".read": true,
".write": true
}
}
These rules don't allow anyone read or write access to your database.
{
"rules": {
".read": false,
".write": false
}
}
Make sure that your code is reading the security rules for the chosen database, such as Cloud Firestore database or Firebase Realtime Database, which are totally distinct databases and use different security rules to control access. Also, check if your user is first authenticated before fetching data from the DB.
来源:https://stackoverflow.com/questions/57885178/firebase-database-access-denied-with-permission-set