Firebase fetching data works with simulator but not in app - security

I've a problem with Firebase security rules. I'm storing data in the following way:
firebase_url
appname
datapath
uid
data1
data2
data3
...
And the security rules:
{
"rules": {
"appname": {
"datapath": {
"$uid": {
".read": "auth != null && auth.uid == $uid",
".write": "auth != null && auth.uid == $uid"
}}}}}
When I login with a user I always get permission denied to the specific path. For example:
FIREBASE WARNING: on() or once() for "FIREBASE_URL/appname/datapath/15" failed: Error: permission_denied: Client doesn't have permission to access the desired data.
Exception: Uncaught Error: Error: permission_denied: Client doesn't have permission to access the desired data.
15 is the user id for which I try to fetch the data.
Using the simulator works flawlessly with the above url and with the user logged in but not from my web app which uses Firebase-Dart. Setting .read and .write simply to true "solves" the problem. I'm trying to read all the data from that url.
Any ideas? Thanks!

Related

DOMException: The request is not allowed by the user agent or the platform in the current context

When attempting to access a file using the getFile() method of a FileSystemFileHandle, this error can occur: DOMException: The request is not allowed by the user agent or the platform in the current context.
This code works:
async function getTheFile() {
// open file picker
[fileHandle] = await window.showOpenFilePicker(pickerOpts);
// get file contents
const fileData = await fileHandle.getFile();
}
However if you store the fileHandle (using IndexedDB, for example) with the intent to persist access to the file even after a page refresh, it will not work. After refreshing the page, the getFile() method will throw the DOMException error.
What's going on?
When the page is refreshed (or all tabs for the app are closed) the browser no longer has permission to access the file.
There is a queryPermission() method available for the FileSystemHandle that can be used to determine whether the file can be accessed before attempting to read it.
Example from a helpful web.dev article:
async function verifyPermission(fileHandle, readWrite) {
const options = {};
if (readWrite) {
options.mode = 'readwrite';
}
// Check if permission was already granted. If so, return true.
if ((await fileHandle.queryPermission(options)) === 'granted') {
return true;
}
// Request permission. If the user grants permission, return true.
if ((await fileHandle.requestPermission(options)) === 'granted') {
return true;
}
// The user didn't grant permission, so return false.
return false;
}
This function will return true once the permission === granted or re-prompt the user for access if permission is either denied or prompt.
Note that you will have to grant permissions each time you retrieve a handle from indexedDB, serializing seems to lose the permission

Firebase Realtime Database Rules Denying Permission

I have an application I am building in Kotlin, with a backend in Node.js.
I am allowing users to login/signup using Firebase Authentication on the client application.
Part of the functionality of the application, is to let users save data online through Firebase's real time database. What happens inside the application, is once a user is logged in, I am passing his/her uid to the backend, which makes a request to the database.
Everything was working fine when the rules for the database were to allow read/write to everyone.
Once I changed them to this:
{
"rules": {
"users": {
".read": "auth != null && auth.uid != null",
".write": "auth != null && auth.uid != null",
}
}
}
I keep getting Permission Denied.
I have tried different variations of the rules:
Without the users key
Only checking that auth is not null
but none seem to work.
Is there some step I am missing?
I have combed over many similar StackOverflow questions and Firebase's real time database documentation, but have not found an answer to my problem.
Some code for reference:
Backend:
app.get('/someRoute', function (req, res) {
var database = firebase.database()
var uid = req.query.uid
database.ref('/users/' + uid).once('value')
.then(function(snapshot) {
var data = snapshot.val() ? snapshot.val() : []
res.status(200).send({ response: data})
}).catch(function(error) {
console.log(error)
res.status(500).json({ error: error})
})
})
Client:
fun loginUser(view : View) {
FirebaseAuth.getInstance().signInWithEmailAndPassword(userEmail, userPassword)
.addOnCompleteListener(this) { task ->
if (task.isSuccessful) {
updateFirebaseUserDisplayName()
} else {
Toast.makeText(this, "An error has occurred during login. Please try again later.", Toast.LENGTH_SHORT).show()
}
}
}
fun updateFirebaseUserDisplayName() {
FirebaseAuth.getInstance().currentUser?.apply {
val profileUpdates : UserProfileChangeRequest = UserProfileChangeRequest.Builder().setDisplayName(userEmail).build()
updateProfile(profileUpdates)?.addOnCompleteListener(OnCompleteListener {
when(it.isSuccessful) {
true -> apply {
Intent(this#LoginActivity, MainActivity::class.java).apply {
startActivity(this)
finish()
}
}
false -> Toast.makeText(this#LoginActivity, "Login has failed", Toast.LENGTH_SHORT).show()
}
})
}
}
After some searching, I found the solution to my problem.
It appears that because I was authenticating users on the client and having a backend that communicated with Firebase's Realtime Database, I had to use Firebase's Admin SDK in the backend.
This is because it was required to pass a unique token generated each time a user logs in and authenticates in the client. This token is then required to be sent to the backend and used when trying to access the Realtime Database.
For anyone else that will stumble upon this question and want to know how it can be done, follow the links below:
Adding Firebase Admin SDK
Verifying ID Tokens
Medium Article Explaining Everything
Also, make sure to reference your database name correctly
It seems a problem with the permission you have given to your node i.e users and try below way
Only authenticated users can access/write data
{
"rules": {
".read": "auth != null",
".write": "auth != null"
}
}

Firebase database access denied with permission set

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?
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.
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.

Firebase security rules for nodejs app

I am new to firebase. I have below given code -
var config = {
apiKey: ".....",
authDomain: "....",
databaseURL: ".....",
storageBucket: "...."
};
firebase.initializeApp(config);
function writeData(socialType, userName, name, message) {
database.ref("/" + socialType + "/" + userName + "/").set({
name: name,
message: message
});
}
writeData("fb", "Jhon", "booo1", "message1");
I wonder what should be the security rules for these kind(i mean access with api key, and there is no login. In fact on firebase nodejs library firebase.auth() the auth method doesn't exist). I don't want to allow anonymous access, I want to let access if there is a valid api key.
{
"rules": {
".read": "auth != null",
".write": "auth != null"
}
}
For this security rules I get permission_denied error. Which probably makes sense, because there is no auth since I am not authenticating, but accessing through api key.
NOTE: I don't want to use service account. I want to use api key to access (read/write) data
The API key just identifies your project to the Firebase servers. It does not authenticate your node.js process with Firebase, nor does it grant you permission to access any data in the database.
To authenticate from your node.js process with Firebase, you will have to follow one of the methods outlined in the documentation. All of these require the use of a service account in some way, either to directly access the database or to sign a custom token. This is simply the way Firebase Authentication works for server-side processes and there is no way to avoid it.
The current issue with your rules is the:
{
"rules": {
".read": "auth != null",
".write": "auth != null"
}
}
Since it appears that you don't have a login. If the browser code has the proper api keys you can use the firebase anonymous auth to allow auth !== null to succeed.
Also in the future I would suggest using the !== over the != to improve reliabilty
You have to manually call the signInWithEmail method on each endpoint before you access a firestore collection. Its annoying but no workaround
return await firebase.auth().signInWithEmailAndPassword("###", "###").then(async () => {
const user = await firebase.firestore().collection('user_info').doc('123').get()
return user.data();
})

firebase server Authenticate with admin privileges

I am trying to setup firebase server using nodejs as discussed Here. However my code is not able to read or wite data from/to any tables using admin privileges even the ones with public read and write access.
This is my code from the server side:
var firebase = require('firebase');
firebase.initializeApp({
serviceAccount: "./App.json",
databaseURL: "https://userPosts.firebaseio.com"
});
//
var db = firebase.database();
var ref = db.ref("testPosts");
ref.once("value", function(snapshot){
console.log(snapshot.val());
}, function(error){
console.log(error);
});
The code below is how the security is set on the server:
"rules": {
"users": {
"$uid": {
".read": "auth != null && auth.uid == $uid",
".write": "auth != null && auth.uid == $uid"
}
},
"testPosts": {
".read": "auth != null",
".write": "auth != null"
}
}
If I comment the serviceAccount line on the server side, the data will be read with no issue and access will be denied when I try to access a node that it does not have access ( which is normal behaviour).
My understanding of Firebase admin privileges is that I should have access to all table nodes regardless of the access privilege.
I would really appreciate if someone can help with this issue. I have been stuck on it for a while now.
-----------------------Edit----------------
I have enabled debugging by adding
firebase.database.enableLogging(true);
and I get the following error:
p:0: Listen called for /posts default
p:0: Making a connection attempt
p:0: Failed to get token: Error: Error refreshing access token: invalid_grant (Invalid JWT: Token must be a short-lived token and in a reasonable timeframe)
p:0: data client disconnected
p:0: Trying to reconnect in 77.64995932509191ms
0: onDisconnectEvents
Can anybody help with the error and what it really means?
After some diggings, it appears that my server time was the problem.
The time of my server was not in sync with the Network Time Protocol (NTP).
In case someone is experiencing the same issue, the following link can be useful.

Resources