0

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

Frank van Puffelen
  • 565,676
  • 79
  • 828
  • 807
Ritu
  • 518
  • 2
  • 12
  • 35

1 Answers1

1

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();
            }
        }
    });
Frank van Puffelen
  • 565,676
  • 79
  • 828
  • 807
  • Even the basic `{ "rules": { ".read": "auth.uid != null", ".write": "auth.uid != null" } } ` get permission denied message. Why? and how can i debug the auth.uid value – Ritu Feb 19 '23 at 17:11
  • That sounds like no user is signed in. Run `Log.i("Firebase", FirebaseAuth.getInstance().getCurrentUser());` right before you try to access the database. – Frank van Puffelen Feb 19 '23 at 18:01
  • Is my custom token with JWT sign up with firebaseAuth is correct? – Ritu Feb 19 '23 at 18:36
  • You can test that yourself by checking what result you get in the `onComplete` of your `signInWithCustomToken`. If the sign in completes successfully your JWT is correct. If the sign in fails, there's an error message in `task.getException()` that tells you what went wrong. – Frank van Puffelen Feb 19 '23 at 20:11
  • Yes if you notice the `firebaseAuth.signInWithCustomToken` i only letting user to proceed (login) if the method calls `onComplete` – Ritu Feb 19 '23 at 20:22
  • A **completed** task can still have **failed** or **succeeded**. https://developers.google.com/android/guides/tasks – Frank van Puffelen Feb 19 '23 at 20:24
  • I checked the link and I'm using `onComplete` and then again i check `if(task.isComplete())` . So by this code i guess it's getting success in signing. But if you can shed some light on my logic that would be really helpful for me. – Ritu Feb 19 '23 at 20:39
  • Guessing it not going to help here. As said: a **completed** task can still have **failed** or **succeeded**. The link I gave has examples for explicit `addOnSuccessListener` and `addOnFailureListener` listener calls, but also for the `addOnCompleteListener` that you use and how to check `isSuccessful` in there. – Frank van Puffelen Feb 19 '23 at 22:01
  • I added an example of this to my answer. – Frank van Puffelen Feb 20 '23 at 00:19
  • You are right and i think i need to ask separate question for help in generating token. I think i have found the root cause After using your code: `com.google.android.gms.tasks.RuntimeExecutionException: com.google.firebase.auth.FirebaseAuthInvalidCredentialsException: The custom token format is incorrect. Please check the documentation.` – Ritu Feb 20 '23 at 01:23