Firebase Realtime Database Rules Denying Permission - node.js

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"
}
}

Related

Identity Platform / Firebase Error (auth/invalid-refresh-token)

I am in the process of upgrading an existing working Firebase Auth project to Identity Platform to benefit from the goodness of tenants.
I am currently testing this against the local emulator and am facing the following issues:
My users no longer show up in the emulator. I reckon, however, that
the behaviour is expected since I am creating users against a tenant
and no longer the default project users "pool"
The users do not show
up in the GCP console either. Yet, the getUserByEmail() method
in a Cloud Function returns the registered users. I therefore have no clue where these users are currently created...
Authenticating users via generateSignInWithEmailLink() works fine.
However, a few steps in the funnel after this, when using the await user?.getIdToken(true) method, I am getting the following error: Uncaught (in promise) FirebaseError: Firebase: Error (auth/invalid-refresh-token) and can't figure out why.
Interestingly, the user.getIdTokenResult() method works fine and does not yield any error.
My entire snippet:
const getCurrentUser = async (): Promise<Auth["currentUser"]> => {
return new Promise((resolve, reject) => {
const unsubscribe = onAuthStateChanged(
auth,
async (user) => {
if (user) {
if (document.referrer.includes("stripe")) {
// console.log({ user });
await user?.getIdToken(true);
console.log({ after: user });
}
state.isAuthenticated.value = true;
state.user.value = user;
try {
const { claims } = await user.getIdTokenResult();
state.claims.value = claims;
if (typeof claims.roles === "string") {
if (claims.active && claims.roles.includes("account_owner")) {
state.isActive.value = true;
}
}
} catch (e) {
console.log(e);
if (e instanceof Error) {
throw new Error();
}
}
}
unsubscribe();
resolve(user);
},
(e) => {
if (e instanceof Error) {
state.error.value = e.message;
logClientError(e as Error);
}
reject(e);
}
);
});
};
For reference, I am working with a Vue 3 / Vite repo.
Any suggestion would be welcome,
Thanks,
S.
Just a quick follow-up here for anyone looking for an answer to this.
I raised a bug report on the firebase-tools Github and:
Users not appearing in the Emulator UI: behaviour confirmed by the firebase team. The emulator does not not support multi-tenancy at the moment. In my experience, however, working with the emulator with multi-tenants, the basic functionalities seem to work: creating users, retrieving them. Impossible however to visualise them or export them.
Refresh token error: bug confirmed by the firebase team and in the process of being triaged. Will likely take some time before being fixed (if ever?). So for now, even if far from being ideal, I have added conditional checks to my code to skip the force refresh of the token on localhost. Instead, I log out and log back in with the users I am expecting to see some changes in the claims for, as this process does not error. Another solution would be to use an actual Firebase Auth instance and not the local emulator, but it feels a bit messy to combine localhost/emulator resources and actual ones, even with a dev account.
The GH issue: here.

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.

How to refresh token in Adal?

Hi I am developing web application in Angular 5. I am using azure active directory authentication oauth 2.0 implicit flow for Login. After 1 hour my application redirects to login page because i handled this in code as below.
protected handleError(error, continuation: () => Observable<any>) {
if (error.status == 401) {
this.appContextService.redirectForLogin();
//return Observable.throw(error);
}
else if (error.url && error.url.toLowerCase().includes(this.loginUrl.toLowerCase())) {
return Observable.throw((error.error && error.error.errormessage) ? `session expired (${error.error.errormessage})` : 'session expired');
}
else {
return Observable.throw(error);
}
}
redirectForLogin() {
this.loginRedirectUrl = this.router.url;
this.router.navigate(['']);
this.adalSvc.login();
}
After one hour i want user not to enter password again. I want to implement refresh token mechanism here. Can someone help me to fix this. Thank you.

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