Issue
Am using the firebase database as my storage. While signing up am storing the username for validating the uniqueness of the username. I declared firebase security rules as follow,
{
"rules": {
".read": true,
".write": true
}
}
And my database structure is as follow,
Users
o**kcarw***27p**enV**UcB3***
Email: "[email protected]"
UserName: "GokulNew"
But if I declare as above am getting a firebase warning as "Your project's Realtime Database "Project" has insecure rules ". So later I changed the security rules as follow,
{
"rules": {
".read": "auth !=null",
".write": "auth !=null"
}
}
If I declare as above the security warning is not showing but it is not allowing me to signup due to the security rules. So again I changed the rule as follow,
{
"rules": {
".read": "auth !=null",
".write": "auth !=null",
"Users":{
".read": true,
".write": true
}
}
}
After this, it is allowed me to signup but a security warning is showing. How can I avoid the security warning and at the same time I need to validate the username while signup. And below is the Java code for signup
Query usernameQuery = FirebaseDatabase.getInstance().getReference().child("Users").
orderByChild("UserName").equalTo(upperCaseChar(username));
usernameQuery.addListenerForSingleValueEvent(new ValueEventListener() {
@Override
public void onDataChange(@NonNull DataSnapshot snapshot) {
if (snapshot.getChildrenCount() > 0) {
usernameLayout.setError("Username is already taken");
vibrator.vibrate(VibrationEffect.createOneShot(200, VibrationEffect.DEFAULT_AMPLITUDE));
dialog.dismiss();
Toast.makeText(RegisterActivity.this, "Username is already taken, Choose new one", Toast.LENGTH_SHORT).show();
} else {
//sign-up and storing the data to firebase
}
}
}
Hope my requirement is clear and thanks in advance,
Solution
Your rules should follow the principle of least privilege, meaning they allow exactly what your code needs and nothing more. Since the code you shared performs a query on /Users
, you want to secure for that query, which would look something like this:
...
"Users": {
".read": "query.orderByChild == 'UserName' &&
query.equalTo.length > 0"
}
...
This only allows (if it works, because I didn't test the length check) reads on /Users
that query on UserNames
and specify an equalTo
value, so that matches your code.
I'd recommend considering another data structure for this though, as your current code may not work when multiple users try to claim the same name around the same time. To make that work, you should introduce a top-level node where you use the username as the key and then use a transaction to write there, and in security rules ensure a value can only be written if non exists yet.
For more on this, also see these previous questions about unique usernames.
Answered By - Frank van Puffelen
Answer Checked By - Willingham (JavaFixing Volunteer)