I have an app where I am using a custom authentication method. First, on the user login into the app, I generate the JWT Token on the server and send it back to the app.
function generateJWT($con,$userID,$cretedTime) {
$secret_Key = "-----BEGIN PRIVATE KEY-----\HCPW\nAtY9K1/19yScEhdmhw8Ozek=\n-----END PRIVATE KEY-----\n";
$date = time();
//$expire_at = $date->modify('+3 minutes')->getTimestamp(); // Add 60 seconds
$domainName = "firebase-adminsdk-XXXXXXXX.iam.gserviceaccount.com";
$request_data = [
'iss' => $domainName,
'sub' => $domainName,
'aud' => "https://identitytoolkit.googleapis.com/google.identity.identitytoolkit.v1.IdentityToolkit",
'iat' => $date, // Issued at: time when the token was generated
// Issuer
//'exp' => $date+(60*60), // Maximum expiration time six month in seconds //15778476
'uid' => $userID, // User name
'created' => $cretedTime, // User name
];
$newToken = JWT::encode($request_data,$secret_Key,'RS256');
return $newToken;
}
Then In the app send on receiving this token I am start the login process.my app using custom firebase auth
firebaseAuth = FirebaseAuth.getInstance();
firebaseAuth.signInWithCustomToken(Session.getJWT())
.addOnCompleteListener(new OnCompleteListener<AuthResult>() {
#Override
public void onComplete(#NonNull Task<AuthResult> task) {
if (task.isComplete()){
if(getActivity()!=null){
//User Logged In Successfully
}else{
// Failed
}
}
}
});
So after days of googling, I got the Firebase rules for the structure of my database to look like this
{
"Chat": {
"206-4": "",
"311-158": "",
"215-112": "",
"734-115": "",
"734-55": "",
"734-468": "",
"34-32": "",
"534-179": "",
"734-345": {
"-NI7hqW3YTFKpnSZU422": {
"Message": "Test",
"Message_From": "Support ",
"Message_From_ID": "4",
"Message_To": "Demo",
},
"-NMVOwlAqmyIA52QU9F-": {
"Message": "Hi",
"Message_From": "Support ",
"Message_From_ID": "4",
"Message_To": "Demo",
}
},
"347-234": {
"-NI7hXybU02Mg6vYqdKp": {
"Message": "Ohio",
"Message_From": "Elaxer Support ",
"Message_From_ID": "4",
"Message_To": "Demo 2",
}
},
"281-69": "",
"317-34": ""
},
"Users": {
"4": {
"Online": false,
"lastSeen": "1675785660782"
},
"284": {
"Online": false,
"lastSeen": "1673611185873"
}
},
"UsersLocations": {
"4-210": {
"-1": {
"Latitude": "22.605",
"Longitude": "88.375"
}
},
"25-21": {
"-1": {
"Latitude": "22.605",
"Longitude": "88.375"
}
}
}
}
Firebase Rules
{
"rules": {
"Chat": {
"$room_id": {
".read": "auth.uid === $room_id && $room_id.beginsWith(auth.uid + '-') || auth.uid === $room_id && $room_id.endsWith('-' + auth.uid)",
".write": "auth.uid === $room_id && $room_id.beginsWith(auth.uid + '-') || auth.uid === $room_id && $room_id.endsWith('-' + auth.uid)"
}
},
"Users": {
"$uid": {
".write": "$uid === auth.uid"
}
},
"UsersLocations": {
"$user_location_id": {
".read": "auth.uid === $user_location_id && $user_location_id.endsWith('-location')",
".write": "auth.uid === $user_location_id && $user_location_id.endsWith('-location')"
}
}
}
}
So when ever i tried to create or get the Chat node (Chatroom).
DatabaseReference db = FirebaseDatabase.getInstance().getReference().child("Chat");
db.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
It gives me error
Listen at /Chat failed: DatabaseError: Permission denied
I am not able to understand why i am getting this error when i am only checking the user id exist in room name and my jwt token on generation time having user id of one user. Please help me out, what's wrong i am doing with my rules
As Alex also answered: you're not granting anyone read access to /Chat, so any code trying to read from there will be rejected. I would not recommend his answer though, as the rule on /Chat makes the more strict rules on /Chat/$room_id meaningless.
I recommend reading the documentation on rules don't filter data (which explains why your current code don't work and on the fact that permissions cascade (which explains why the rules in Alex' answer make the ones you already have meaningless).
The data structure you have look like what I described in my answer to: Best way to manage Chat channels in Firebase. In my answer there I also showed how to model security rules to allow read access and how to get a list of the chat room for the user, so I recommend checking that out.
As I tried explaining in the comments, the way you handle the sign-in result is wrong:
// ❌ THIS IS WRONG ❌
firebaseAuth = FirebaseAuth.getInstance();
firebaseAuth.signInWithCustomToken(Session.getJWT())
.addOnCompleteListener(new OnCompleteListener<AuthResult>() {
#Override
public void onComplete(#NonNull Task<AuthResult> task) {
if (task.isComplete()){
if(getActivity()!=null){
//User Logged In Successfully
}else{
// Failed
}
}
}
});
Instead, when a Task completes you should check whether it succeeded or failed, as shown in the documentation on handling tas results. In your case that'd be:
// ✅ Handling success and failure correctly 👍
firebaseAuth = FirebaseAuth.getInstance();
firebaseAuth.signInWithCustomToken(Session.getJWT())
.addOnCompleteListener(new OnCompleteListener<AuthResult>() {
#Override
public void onComplete(#NonNull Task<AuthResult> task) {
if (task.isSuccessful()!=null) {
// User signed in
// TODO: access database
} else {
// Sign in failed
throw task.getException();
}
}
});
Related
I am trying to count the number of children in my DB table that meet a certain condition. If the isSeen column equals false then I want to count that, if it equals true I don't want to count it.
Currently, it's not working but if I change the query from Query query = usersRef.orderByChild("isSeen").equalTo(true); to Query query = usersRef.orderByChild("isSeen"); I get a number but it's not the correct way. Can someone please help me?
Rules:
{
"rules": {
".read": true,
".write": true,
"Messages": {
".indexOn": "isSeen"
}
}
}
"Messages": {
"b3vYlKZFrje0e3wHyBlWIK4ooK93": {
"DIt5bGqw2WS4eGHNqQJKxZSn3B72": {
"-N8NCgnwX6V7ghfGlcWS": {
"dateAdded": 1659337356887,
"date_time": "Aug-01-2022 3:02:36 AM",
"from": "DIt5bGqw2WS4eGHNqQJKxZSn3B72",
"isSeen": true,
"message": "Yoo",
"to": "b3vYlKZFrje0e3wHyBlWIK4ooK93",
"type": "text"
},
"-N99iQjlMfeyOM_VCAEB": {
"dateAdded": 1660184797462,
"date_time": "Aug-10-2022 10:26:37 PM",
"from": "DIt5bGqw2WS4eGHNqQJKxZSn3B72",
"isSeen": true,
"message": "Wassup",
"to": "b3vYlKZFrje0e3wHyBlWIK4ooK93",
"type": "text"
}
}
}
},
DatabaseReference usersRef = FirebaseDatabase.getInstance().getReference("Messages");
Query query = usersRef.child(firebaseUser.getUid()).orderByChild("isSeen").equalTo(true);
query.get().addOnCompleteListener(new OnCompleteListener<DataSnapshot>() {
#Override
public void onComplete(#NonNull Task<DataSnapshot> task) {
if (task.isSuccessful()) {
long count = task.getResult().getChildrenCount();
Log.d("TAG1", "count: " + count);
} else {
Log.d("TAG2", task.getException().getMessage()); //Never ignore potential errors!
}
}
});
Error message:
D/TAG2: Index not defined, add ".indexOn": "isSeen", for path "/Messages/b3vYlKZFrje0e3wHyBlWIK4ooK93", to the rules
Database schema
When you're using the following query:
Query query = usersRef.orderByChild("isSeen").equalTo(true);
Firebase will always return the exact data you are querying, meaning that you'll get all elements that have the isSeen field set to true. Please note that there is no way you can query by a negation. So something like this is not possible:
Query query = usersRef.orderByChild("isSeen").notEqualTo(true);
// 👆
According to your comment in which you say that you don't have any elements where the isSeen field is set to true, then your query will yield no results, and that's the expected behavior.
While #TimothyPham's answer will work, using getChildrenCount() might be the best solution. Why? Because if you have a lot of messages this operation requires you to read all of them in order to provide a number. The best solution I can think of would be to increment/decrement a counter as explained in my answer from the following post:
How to save users score in firebase and retrieve it in real-time in Android studio
Edit:
Query query = usersRef.orderByChild("isSeen").equalTo(true);
query.get().addOnCompleteListener(new OnCompleteListener<DataSnapshot>() {
#Override
public void onComplete(#NonNull Task<DataSnapshot> task) {
if (task.isSuccessful()) {
long count = task.getResult().getChildrenCount();
Log.d("TAG", "count: " + count);
} else {
Log.d("TAG", task.getException().getMessage()); //Never ignore potential errors!
}
}
});
But this code will only work if you have elements in the database that have he isSeen field is set to true.
I think you should try this, it working for me. It read your messages for the first time
usersRef.orderByChild("isSeen").equalTo(true).addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot snapshot) {
Log.d("TAG", "Count:" + String.valueOf(dataSnapshot.getChildrenCount()));
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
Try this:
Log.e(dataSnapshot.getKey(),dataSnapshot.getChildrenCount() + "");
So in my database, I have 2 nodes Customer and Driver. And I tried to make the login page in such a way that if the user is in the driver node he goes to the page meant for driver and the same way for the customer. My problem is even when it is Driver it is still going to the customer page.
#Override
public void onClick(View v) {
progressdialog.show();
auth.signInWithEmailAndPassword(binding.Email.getText().toString(),binding.Password.getText().toString())
.addOnCompleteListener(new OnCompleteListener<AuthResult>() {
#Override
public void onComplete(#NonNull Task<AuthResult> task) {
progressdialog.dismiss();
if(task.isSuccessful()){
Toast.makeText(Login_Page.this, "Success", Toast.LENGTH_SHORT).show();
String uid = auth.getInstance().getCurrentUser().getUid();
DatabaseReference rootRef = FirebaseDatabase.getInstance().getReference();
DatabaseReference uidRef = rootRef.child("Driver");
ValueEventListener valueEventListener = new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot snapshot) {
if(snapshot.exists()) {
Toast.makeText(Login_Page.this,"LoginDriver",Toast.LENGTH_SHORT);
Intent intent=new Intent(Login_Page.this, HomePage.class);
startActivity(intent);
}
else {
startActivity(new Intent(Login_Page.this, Custhomepage.class));
}
}
#Override
public void onCancelled(#NonNull DatabaseError error) {
}
};
uidRef.addListenerForSingleValueEvent(valueEventListener);
}
In this code, When the child is Driver it is supposed to go to the home page but instead, it goes to custhomepage even when the node is in Driver.
This is the database values:
{
"Users" : {
"Customer" : {
"5JEgU9isvdWFop4yt0lGFt9iSEo1" : {
"mail" : "Ashh#bbyshark.com",
"password" : "AlexXalexa",
"username" : "Ashhh"
},
"VyxUiy60khMEr4LPsNiKRTIxRs72" : {
"mail" : "xxx#g.com",
"password" : "yvuuuuuu",
"username" : "xxx"
},
"aRmz8jnue1NeDeGA08ey9H4rvIJ3" : {
"mail" : "aa#gmail.co.cin",
"password" : "123344555",
"username" : "aa"
},
"kgG9GBSK6xVdQUdTKEAnuU3OROv1" : {
"mail" : "tttt#class.com",
"password" : "---------------",
"username" : "j need"
},
"pWnRecYxRlQMwUJH5bZ10My5cBi1" : {
"mail" : "Ashh#babyshark.com",
"password" : "AlexXalexa",
"username" : "Ashhh"
},
"qHctsriB2GZYzQ2NULw0PmBdCkV2" : {
"mail" : "arfan#gmail.com",
"password" : "123456yus",
"username" : "Arfan"
}
},
"Driver" : {
"ZMqeFvM4gTh2vjTVu31JQY2jz9x2" : {
"mail" : "aww#gmail.com",
"password" : "696969",
"username" : "aditya"
}
}
}
}
Looks like you're adding the ValueEventListener to the entire Driver node so it is seeing if a snapshot exists at "Users/Driver" which it does. Instead the ValueEventListener should be attached to the child node using the UID to check if a snapshot exists at "Users/Driver/{UID}".
Changing this line:
DatabaseReference uidRef = rootRef.child("Driver");
To this:
DatabaseReference uidRef = rootRef.child("Users").child("Driver").child(uid);
Should solve the issue.
I have a Firebase realtime database with a layout similare to this:
{
"data": {
"index_1": {
"xxx": {
"value_1": "Some data",
"value_2": true
},
"index_2": {
"xxx": {
"value_1": "Some data",
"value_2": true
},
"index_3": {
"xxx": {
"value_1": "Some data",
"value_2": true
},
},
"keys": {
"user1": {
"index_1": true,
"index_2": true
},
"user2": {
"index_1": true,
"index_3": true
}
}
}
And a simple java class:
public class Data {
private String value_1;
private Boolean value_2;
...
getters/setters
...
}
Is there any (easy) way to get objects from /data/{index}/xxx/{object}?
If the layout had been /data/{index}/{object}, eg:
"data": {
"index_1": {
"value_1": "Some data",
"value_2": true
}
}
it's is possible to use
keyQuery = FirebaseDatabase.getInstance()
.getReference("keys")
.orderByKey();
dataRef = FirebaseDatabase.getInstance().getReference("data");
FirebaseRecyclerOption<Data> options = new FirebaseRecyclerOptions.Builder<Data>().
.setIndexedQuery(keyQuery, dataRef, Data.class)
.build();
but I havn't found any way to do this when the index isn't just before the wanted data.
I found a way to solve it using FirebaseRecyclerOption by creating a custom SnapshotParser
SnapshotParser<Data> parser = new SnapshotParser<Data>() {
#NonNull
#Override
public Data parseSnapshot(#NonNull DataSnapshot snapshot) {
return snapshot.child("xxx").getValue(Data.class);
}
}
FirebaseRecyclerOption<Data> options = new FirebaseRecyclerOptions.Builder<Data>().
.setIndexedQuery(keyQuery, dataRef, parser)
.build();
Is there any (easy) way to get objects from /data/{index}/xxx/{object}?
Yes it is, using the following lines of code:
DatabaseReference rootRef = FirebaseDatabase.getInstance().getReference();
DatabaseReference dataRef = rootRef.child("data");
ValueEventListener valueEventListener = new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
for(DataSnapshot ds : dataSnapshot.getChildren()) {
String value_1 = ds.child("xxx").child("value_1").getValue(String.class);
boolean value_2 = ds.child("xxx").child("value_2").getValue(Boolean.class);
Log.d(TAG, value_1 + " / " + value_2);
}
}
#Override
public void onCancelled(#NonNull DatabaseError databaseError) {
Log.d(TAG, databaseError.getMessage()); //Don't ignore errors!
}
};
dataRef.addListenerForSingleValueEvent(valueEventListener);
If you want to use FirebaseRecyclerOption, you database schema should indeed looks like this:
"data": {
"index_1": {
"value_1": "Some data",
"value_2": true
}
}
Without that extra xxx child.
Parse will shut down at the end of the year, so I decided to start using Firebase. I need to implement a register process with 3 fields : email, username, password (Email & username must be unique for my app).
Since, Firebase is not providing an easy way to manage username like Parse, I decided to use only the email/password registration and save some additional data like username. Here is my users data structure :
app : {
users: {
"some-user-uid": {
email: "test#test.com"
username: "myname"
}
}
}
But, what I want to do is to make the username unique and to check it before creating an account.
These are my rules :
{
"rules": {
".read": true,
".write": true,
"users": {
"$uid": {
".write": "auth !== null && auth.uid === $uid",
".read": "auth !== null && auth.provider === 'password'",
"username": {".validate": "!root.child('users').child(newData.child('username').val()).exists()"}
}
}
}
}
Thank you very much for your help
Part of the answer is to store an index of usernames, that you check against in your security rules:
app : {
users: {
"some-user-uid": {
email: "test#test.com"
username: "myname"
}
},
usernames: {
"myname": "some-user-uid"
}
}
So the usernames node maps a username to a uid. It essentially reads as "username 'myname' is owned by 'some-user-uid'".
With this data structure, your security rules can check if there is already an entry for a given username:
"users": {
"$uid": {
".write": "auth !== null && auth.uid === $uid",
".read": "auth !== null && auth.provider === 'password'",
"username": {
".validate": "
!root.child('usernames').child(newData.val()).exists() ||
root.child('usernames').child(newData.val()).val() == $uid"
}
}
}
This validates that the username isn't claimed by anyone yet OR it is claimed by the current user.
Save usernames as suggested by Frank but when you save usernames, use runTransaction function in Firebase to make sure that the username is not taken. This function is guaranteed by Firebase to be an atomic operation so you can be rest assured of no collision
firebaseRef.child("usernames").child(username).runTransaction(new Transaction.Handler() {
#Override
public Transaction.Result doTransaction(MutableData mutableData) {
if (mutableData.getValue() == null) {
mutableData.setValue(authData.getUid());
return Transaction.success(mutableData);
}
return Transaction.abort();
}
#Override
public void onComplete(FirebaseError firebaseError, boolean commited, DataSnapshot dataSnapshot) {
if (commited) {
// username saved
} else {
// username exists
}
}
});
make a new branch for username and when new user login get list of all username and check wether it is present in db or not if it is present show them toast otherwise put its username in the username branch ..
I dont know much about firebase security yet, but I may have solved the problem using Java. I have posted it below.
my data structure is
myapp
{
users: {
<unique generated-id>
{ username: "example.username" }
}
}
public boolean isUsernameExists(final String enteredUsername) {
final Boolean[] isExist = {false};
FBref.child("users").addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
for (DataSnapshot userSnapshot : dataSnapshot.getChildren()) {
String existingUsername = (String) userSnapshot.child("userName").getValue();
if (existingUsername.equals(enteredUsername)) {
isExist[0] = true;
}
}
}
#Override
public void onCancelled(FirebaseError firebaseError) {
//some error thrown here
}
});
return isExist[0];
}
I am developing an android App in which I implemented Firebase realtime database. And now I want to login using by email id and password which stored in database. I know firebase auth can be use for authentication but I want to login using realtime database like we do in Sql or my sql.
I done this. First I fetch data of particular email id from database and than compare its password and assign the related function to do..
Query query = databaseReference.child("users").orderByChild("email").equalTo(txvUsername.getText().toString().trim());
query.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
if (dataSnapshot.exists()) {
// dataSnapshot is the "issue" node with all children with id 0
for (DataSnapshot user : dataSnapshot.getChildren()) {
// do something with the individual "issues"
UsersBean usersBean = user.getValue(UsersBean.class);
if (usersBean.password.equals(txvPassword.getText().toString().trim())) {
Intent intent = new Intent(context, MainActivity.class);
startActivity(intent);
} else {
Toast.makeText(context, "Password is wrong", Toast.LENGTH_LONG).show();
}
}
} else {
Toast.makeText(context, "User not found", Toast.LENGTH_LONG).show();
}
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
Thanks..
I am assuming that you know how to use ValueEventListener, onDataChange method and Datasnapshot.
As you stated above, you want to do it like in SQL or MySQL. So instead of using email, I will use username and password.
You can authenticate your user without using Firebase Authentication. However, this is not recommended for the database security.
{
"userList": {
"JRHTHaIsjNPLXOQivY": {
"userName": "userA",
"password": "abc123"
},
"JRHTHaKuIFIhnj02kE": {
"userName": "userB",
"password": "abc123"
}
},
"userAuth": {
"userA": "JRHTHaIsjNPLXOQivY",
"userB": "JRHTHaKuIFIhnj02kE"
}
}
As you can see above I have saved the the userName and userId in firebase db. When a user enter the username and password, you should read the userId by using username in the userAuth. Once you have retrieved the userID, use it to access the userList for the user details. This is where you check for the entered password.
{
"userList": {
"userA": {
"password": "abc123"
},
"userB": {
"password": "abc123"
}
}
}
Or you can just authenticate your users like this.