Firebase Rules to Allow Server to Write - firebase

After a user registers, we create a username for the user under /users child.
How can I setup my rules so that only the server can create new users and user data cannot be modified by other users.
Javascript
<script>
var ref = new Firebase("https://myapp.firebaseio.com");
var usersRef = ref.child('users');
var usernameField = $('#usernameInput');
var emailField = $('#emailInput');
var passField = $('#passInput');
$('#submitBtn').click(function() {
ref.createUser({
email : emailField.val(),
password : passField.val()
}, function(error, userData) {
if (error) {
console.log("Error creating user:", error);
} else {
console.log("Successfully created user account with uid:", userData.uid);
usersRef.push({
uid: userData.uid,
username: usernameField.val()
});
}
});
});
</script>
Current rules:
{
"rules": {
"users": {
".indexOn": "username",
"$uid": {
".write": true,
// grants read access to any user who is logged in with an email and password
".read": "auth !== null && auth.provider === 'password'"
}
}
}
}

To set up something so that "only the server can create new users", you can access your database using their Server SDK. This way, you can set rules that only apply to access to the database from the server.
However, if you want a web-based UI for admins to create new users, you need to write a Node.js or Java backend web server (utilizing the Server SDK). You can't use the client-side JS SDK, or Firebase hosting.
Just follow the instructions in the Server Side setup section of their documentation.
Then add the databaseAuthVariableOverride option to initializeApp:
var firebase = require("firebase");
firebase.initializeApp({
serviceAccount: "path/to/serviceAccountCredentials.json",
databaseURL: "https://databaseName.firebaseio.com",
databaseAuthVariableOverride: {
// maybe use something more descriptive and unique
uid: "server-side-app"
}
});
Then the following as your rules (warning: not tested):
{
"rules": {
"users": {
".indexOn": "username",
// grants write access if the authenticated user is accessing
// via the Server SDK
".write": "auth != null && auth.uid === 'server-side-app'",
// grants read access to any user who is logged in with an email/password
".read": "auth != null && auth.provider === 'password'"
}
}
}

There is no concept of "only the server" when you use Firebase. Each user writes their own data, so to secure that you need to use Firebase's User Based Security.
In this case the rules are fairly simple: you want each user to be able to only write to their own node. You can do this by modifying your rules to:
{
"rules": {
"users": {
".indexOn": "username",
"$uid": {
// grants write access to the owner of this user account
// whose uid must exactly match the key ($user_id)
".write": "auth.uid === $uid",
// grants read access to any user who is logged in with an email and password
".read": "auth.uid !== null && auth.provider === 'password'"
}
}
}
}
Note that I copied the .write rule from the first example in the Firebase documentation on User Based Security. So it's probably a good idea to read that page (again), since it explains a lot more than just the answer to this question.

Related

Firebase realtime database write rules

I have a question about the firebase realtime database rules.
Somebody creates an account and that account creates a path in the realtime database:
The structure is simple (key, userid, other data).
This are my rules:
{
/* Visit https://firebase.google.com/docs/database/security to learn more about security rules. */
"rules": {
"waitingForApproval": {
"$uid": {
".write": true,
".read": "auth != null && $uid === auth.uid"
}
},
}
}
But now comes the question. How can I allow to let users write to this object? Everyone who has the code (see BQUyhq)w3D) can write to the object id. They can't write to it when they don't have the code.
Is something possible like that? If so, how can I do that.
I can think of two ways to approach something like this:
1. Using a cloud function:
Set the rule for writing to false and use a cloud function to make updates. The cloud function would take the code as an input, verify it matches the expected code, and perform the updates on the user's behalf. E.g.:
const functions = require('firebase-functions');
const admin = require('firebase-admin');
exports.updateObject = functions.https.onCall((data, context) => {
const uid = data.uid;
const code = data.code;
const codeRef = admin.database().ref(`waitingForApproval/${uid}/code`);
return codeRef.once('value').then((codeSnapshot) => {
if (codeSnapshot.val() === code) {
// Make updates on the user's behalf
}
});
});
2. Storing user entered code in the DB:
Add a user editable section where users can set their codes and validate against that, e.g.:
The DB after a user has entered a code:
"userCodes": {
"bXUQ6PRNqvOgjwoAlm6HmYuWiYo1": {
"BQUyhq)w3D": true,
},
...
}
Set your rule to check if the user has set the code for the object:
"waitingForApproval": {
"$uid": {
".write": "root.child('userCodes').child($uid).child(data.child('code').val()).val() == true"
".read": "auth != null && $uid === auth.uid"
}
},
Which essentially checks if userCodes/{uid}/{code} is set to true before allowing the write.
(Note that using this method your codes cannot contain the characters that firebase doesn't support in its keys: . $ # [ ] /)

How to save, with anonymous authentication, to Firebase database - using Flutter

I'm getting permission denied after I have authenticated with Anonymous Auth
[ERROR:topaz/lib/tonic/logging/dart_error.cc(16)] Unhandled exception:
E/flutter (21033): PlatformException(-3, Permission denied, )
...
_getCurrentUser: FirebaseUser({providerId: firebase, uid: DOIL...............u54j1, displayName: , email: , isAnonymous: true, isEmailVerified: false, providerData: [{providerId: firebase, uid: DOIL//////////////54j1, displayName: , email: }]})
My rules on the Firebase DB are
{
"rules": {
//Test rule
// "users": {
// "$uid": {
// ".read": "auth != null && auth.uid == $uid",
// ".write": "auth != null && auth.uid == $uid",
// }
// }
// General rule - closed to everyone but app uses
".read": "auth != null",
".write": "auth != null"
}
}
The code I use to save data - works fine with DB rules set to open to all.
_saveUserData(UserEntry userData) async {
print("_saveUserData jsonData =" userData.toJson().toString());
// SAVE MY DATA TO DB
// tUsers = db reference to the child node users on Firebase
_tUsers.push().set(talentUserData.toJson());
}
What am I doing wrong ? Any assistance is appreciated. Thanks.
In the hope of saving someone a headache. I found the problem to be not getting a fresh instance of the Firebase Database. I created the instance in my main.dart and passed it to my HomePage in the constructor (per the example file in the library).
When I got a fresh instance ...out of desperation debugging...it worked. I can now have anonymous login so that only people who have installed the app can write to the database.

Firebase Database Rules for Unique Usernames

I'm trying to create a website that uses Firebase's databases to store user information. I want to use unique usernames. I have two indexes, one for users and another for usernames.
My database is structured like this:
users {
$uid {
username: "username1",
gender: "xyz"
email: "xyz"
}
},
usernames {
"username1": $uid"
}
The users claim a username with their $uid.
These are my rules:
{
"rules": {
"users": {
"$uid": {
".write": "auth !== null && auth.uid === $uid",
".read": "auth !== null && auth.uid === $uid",
"username": {
".validate": "
!root.child('usernames').child(newData.val()).exists() ||
root.child('usernames').child(newData.val()).val() == $uid"
}
}
},
"usernames" : {
".write": "!data.exists() && auth!= null",
".validate": "newData.val() == auth.uid" <---- I can't get this too work
}
}
}
When setting username under $uid it checks the usernames index so username can only be written with a username not in use or one that has it's own $uid.
I only want data in which the value is the authenticated users uid and the key is the username. I can't quite get this to work. I suspect that I am using newData().val() incorrectly. My validate statement is failing.
I'd like to avoid using custom tokens, but I'm open to any suggestions. Thanks in advance.
Sorry if this explanation is too drawn out, this is my second post on StackOverflow.
Edit #2
I did some research from what I can tell and all I can find in docs talks about the need to use .child() before .val() but I need .child to take a variable instead of a set username.
The accepted answer will cause problems as soon as you want to allow people to change their usernames.
By slightly changing your database structure you can easily manage usernames in firebase. You can also allow users to change their username.
Database:
usernames: {
username1: {
owner: "userId1"
},
username2: {
owner: "userId2"
},
...
}
The following rules will:
stop any user changing/deleting another user's username
let a new user create a username
let a user change their username by deleting their old one and creating a new one
(split on newlines only for readability)
"usernames": {
"$username": {
".read": "true",
".write": "auth != null &&
(
\\ Allow user to delete only their own username
(data.exists() && data.child('owner').val() === auth.uid && !newData.child('owner').exists())
||
\\ Allow user to create a new username if the username is not taken
(!data.exists() && newData.child('owner').val() === auth.uid)
)"
}
}
This update will create a user:
const toAdd = {}
toAdd[`usernames/${username}`] = { owner: userId };
firebase.database().ref().set(toAdd)
This update will change a users username:
const update = {}
update[`usernames/${previousUsername.toLowerCase()}`] = null;
update[`usernames/${newUsername.toLowerCase()}`] = { owner: userId };
firebase.database().ref().update(update)
In your code you should also query the database to check if a username exists before sending the update.
Why I avoided the username: userId approach:
You say
Edit #2 I did some research from what I can tell and all I can find in docs talks about the need to use .child() before .val() but I need .child to take a variable instead of a set username.
I believe this is correct. For your usernames setup:
usernames {
"username1": userId1,
"username2": userId2,
...
}
This answer describes a method which sends the key information in the request:
ref.update({
_username: "username3",
username3: "userId3",
})
Then you can check if the username is in the database with a rule like
data.child(newData.child('_username').val()).exists()
However in your usernames setup, you would then overwrite the other usernames with this update. To avoid that you would need to set the new data at the path of the username, usernames/username3 = userId3. But you cannot set it like this as you are then back at the problem of not having the key to reference in the rule.
You would need to create a nonsensical structure something like:
usernames: {
username1: {
_username: "username1",
userId: "userId1"
}
}
So I chose the simpler, but unfortunately a slightly less beautiful username database setup I describe at the start.
Sorry if i'm late but i ran into a similar problem, i changed my usernames rule to the following which did the trick:
"usernames" : {
"$username": {
".write": "!data.exists() && auth!= null && newData.val() == auth.uid"
}
},
}

Firebase Security Rules

I'm new to Firebase and really struggling with the security rules.
I'm trying to store data for anonymous user sessions that can only be accessed by that user but as soon as I tighten up the security rules I get permission denied and can't work out why.
The structure I have is:
/userSessions/$user/user_id
The application pushes to user sessions and writes the user_id as returned by the anonymous login.
I have the following security rules:
{
"rules": {
".read": true,
"userSessions": {
"$user": {
".write": "newData.child('user_id').val() == auth.id",
"user_id": {
".validate": "newData.isString()"
}
}
}
}
}
I'm sure I'm doing something silly, but can't work out how to use the security simulator either so don't know how to go about troubleshooting this
Any help greatly appreciated
EDIT -
Problem occurs when trying to create a new entry under userSessions after authenticating anonymously.
Code:
var userSessionsRef = new Firebase('https://xxxxxx.firebaseio.com/userSessions');
var userSession;
var auth = new FirebaseSimpleLogin( userSessionsRef, function(error,user) {
if (error) {
console.log(error);
} else if (user) {
userSessionsRef.child(user.id).set({
user_id: user.id,
provider: user.provider,
created: Firebase.ServerValue.TIMESTAMP
});
userSession = new Firebase('https://xxxxx.firebaseio.com/userSessions/'+user.id);
userSession.onDisconnect().remove();
}
The set() operation you've provided runs great; no errors; there must be another issue at play here that you haven't captured in your repro (did you enable anonymous auth in Forge?). Here's a fiddle running on your security rules, copied line-for-line (just moved down one child path): http://jsfiddle.net/katowulf/wxn5J/
On a related note, your current security rules will not allow the userSession.onDisconnect().remove(); op, since records can only be written if newData() contains a user_id value (i.e. it can't be null). A better way to structure these security rules would be as follows:
{
"rules": {
".read": true,
"userSessions": {
"$user": {
".write": "$user === auth.id", // delete allowed
"user_id": {
".validate": "newData.isString() && newData.val() === $user"
}
}
}
}
}

Firebase security rule gives permission denied?

I'm struggling to set the proper security rules for my application.
An overview of the application I'm writing is that users can register themselves using email and password (I'm using Firebase Simple Login for this which works perfectly). Once logged in, user can add their todos.
angularFire('https://<firebase>/firebaseio.com/todos', $scope, 'todos');
And to add a new todo against any user, I simply update the todos model.
$scope.todos.push({
user: 'a#b.com',
todo: 'What to do?'
});
This security rules I'm using to restrict non-registered user to add any todo:
{
"rules": {
".read": true,
"todos": {
".write": "auth != null",
".validate": "auth.email == newData.child('user').val()"
}
}
}
But it does not allow even an authenticated user to write any data and throwing an error,
"FIREBASE WARNING: on() or once() for /todos failed: Error: permission_denied."
But If I add the following data in simulator then it works as expected.
{user: "a#b.com", todo: 'What to do?'}
Here is the log:
/todos:.write: "auth != null"
=> true
/todos:.validate: "auth.email == newData.child('user').val()"
=> true
/todos:.validate: "auth.email == newData.child('user').val()"
=> true
Write was allowed.
push adds a new child with a randomly generated ID (in chronological order) to /todos. So, newData isn't pointing to what you think it is pointing to. Change your rules to:
{
"rules": {
".read": true,
"todos": {
"$todoid": {
".write": "auth != null",
".validate": "auth.email == newData.child('user').val()"
}
}
}
}
Update: Above rule is valid but angularFire currently writes the whole array back to the server causing the auth to fail. You can use angularFireCollection instead, to only write the new TODO back, like so:
$scope.todos = angularFireCollection(new Firebase(URL));
$scope.todos.add({user: 'a#b.com', todo: 'What to do?'});
There's an open issue to optimize angularFire's behavior when new items are added to the list, but in the meantime you can use angularFireCollection to get the right behavior.

Resources