how to know if a valid FirebaseAuth is going to expire soon - android

I've a weird behavior in my app related to the firebase database.
I got some unexpected access denied when trying to perform some stuff at database... That could be bug in my code but is a pretty simple code.
I want to ilustrate the sitation with a real scenario, please read the requirements
1- i've only 1 app accessing the database (android)
2- i've only 1 method in the whole app trying to access the specific node which is causing deny of access
3- in my firebase rules the only rule applied to this node is:
".read":"auth != null",
".write":"auth != null"
4- i DO explicity check FirebaseAuth.getInstance().getCurrentUser() != null right before calling the method
this is a pseudo snippet of how my code is (i wont pust the real code simple because is too long)
public void onResume() {
if(FirebaseAuth.getInstance().getCurrentUser() == null)
doLogin();
else
checkFirebaseStuff();
}
As google doesn't provide any info about why the access was rejected the only possible explanation i found is:
the user was authenticated but long time before, so when it checked on if was still valid, but short time later when the function really ran its token (or some other firebase auth check) was no longer valid, so it caused the access denied
this error doesn't happen a lot, i have 5k daily users and it happens around 20 or 50 times a day, but still shouldn't happen even once
does it make sense? can anyone help me with any aditional info?

Hmm, I think you can fix this by checking if the user is disconnected from the firebase database, but I'm not really sure if that will affect the Auth too, you can give it a try
DatabaseReference connectedRef = FirebaseDatabase.getInstance().getReference(".info/connected");
connectedRef.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot snapshot) {
boolean connected = snapshot.getValue(Boolean.class);
if (connected) {
Log.d(TAG, "connected");
//Here you can update your mAuth state
} else {
Log.d(TAG, "not connected");
}
}
#Override
public void onCancelled(#NonNull DatabaseError error) {
Log.w(TAG, "Listener was cancelled");
}
});

Related

Firebase onDataChange returns null value after internet reconnection

I have this code to read data and write the value into an object
if(SpecialFunction.isNetworkAvailable()) {
databaseContent.loadAccountFromDatabase(account -> {
this.account = account;
binding.textView3.setText(account.toString());
});
} else {
startActivity(new Intent(this, InternetTroubleActivity.class));
}
Where databaseContent is a class which contains Firebase logic which I need.
In loadAccountFromDatabase I have next code which works with one problem.
public void loadAccountFromDatabase(FirebaseCallbackAccount accountFirebaseCallback) {
ValueEventListener valueEventListener = new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot snapshot) {
if (snapshot.exists()) {
account = snapshot.getValue(Account.class);
} else {
account = new Account();
setDefaultAccount();
}
accountFirebaseCallback.onCallback(account);
}
#Override
public void onCancelled(#NonNull DatabaseError error) {
Log.e("loadAccountFromDatabase", "Error: " + error.toString());
}
};
database.addValueEventListener(valueEventListener);
}
When I try to read data after internet reconnection or when I turn on internet after onStart() and run loadAccountFromDatabase I get null value of my snapshot.
I have a method (isNetworkAvailable()) which works well and when I don't have internet connection it returns false.
The next part of code doesn't work properly. The snapshot.exists returns true and snapshot.getValue returns null although the value is other.
if (snapshot.exists()) {
account = snapshot.getValue(Account.class);
}
If I run an application with internet connection everything works well while I don't turn off internet and try to read again.
I also tried to use database.get().addOnCompleteListener(onCompleteListener)... but I got the same result. Everything works well while I don't try to read data after internet reconnection.
database.keepSynced(true) also doesn't help.
UPDATE##
Database init:
private final String USER_KEY = "Account", PURCHASES = "purchases";
public void init() {
mAuth = FirebaseAuth.getInstance();
firebaseDatabase = FirebaseDatabase.getInstance();
database = firebaseDatabase.getReference(String.format("%s/%s", USER_KEY, mAuth.getUid()));
}
Structure:
console
UPDATE 2##
To help you understand the problem I add screenshot of log. GREEN box: I run my app with internet connection. The data was read and wrote correct. RED box: I closed my app, destroyed activity, turned off internet. Then I run my app, but method isNetworkAvailable didn't let me run MainActivity. Then I turned on internet and clicked on button. The data was being tried to read but I got object with null variables althrough they are not null. If you think that something is wrong with isNetworkAvailable I tell you that this problem was before I have added it. I was hoping that it would help me to read correct but it didn't help.
logcat
UPDATE 3##
JSON:
{
"Account" : {
"wBOZsnGGywYIpap3cLZodPOWcpt2" : {
"budget" : 100,
"budgetLastMonth" : 0,
"budgetLeft" : 100,
"currencyType" : "USD",
"email" : "bibishka117#gmail.com",
"id" : "wBOZsnGGywYIpap3cLZodPOWcpt2",
"personName" : "новый пользователь"
}
}
}
I found a problem. 4 hours of reading my code and stackoverflow. I don't know how, but in onPause () I had code that writes null object to a database.
So when I tested and turned on / off the Internet, I run it. So I wrote a null object that was not instantly updated on the console, and when I turned on the Internet, the object was updated to zero in the database and then read in my app. :]

Is it a good idea to call Firebase reload() inside OnAuthStateChanged?

Is it a good idea to call Firebase reload() inside OnAuthStateChanged? Initially I would call reload() during app initialization, but it's not guaranteed the FirebaseUser object has been loaded yet. Seems a clean way to do it is call inside OnAuthStateChanged() if the user is not null. The logic being that the returned user profile would match what's cached and no subsequent calls to OnAuthStateChanged will be issued. It works - but I'm a bit worried about a run-away loop for reasons that may not be obvious to me.
#Override
public void onAuthStateChanged(#NonNull FirebaseAuth firebaseAuth)
{
final FirebaseUser user = mAuth.getCurrentUser();
//call reload here???
if (user != null)
{
user.reload().addOnFailureListener(new OnFailureListener()
{
#Override
public void onFailure(#NonNull Exception e)
{
if (e instanceof FirebaseAuthInvalidUserException)
{
Log.e(TAG, "INVALID USER EXCEPTION: " + e);
Disconnect();
SignOut();
}
}
});
}
}
I would not expect reload() to do anything significant at all on a newly signed in user. Since onAuthStateChanged indicates that the user has just signed in, the profile information should have just been loaded from the backend service. Reloading at that point isn't likely to be helpful.
Reloading is intended for times when a user has been signed in for a while, and your code is trying to check if something changed with its profile since it was last signed in.

Real-time database onDisconnect not executing after logging out

I have implemented the Firebase Real-Time Database presence system as shown in the official Firebase documentation. I would like to make the database secure so that logged-in users can only write to their own presence entries in the DB. So, on login, the user writes to the reference path /auth/{authId}/connections and at the same time sets up the onDisconnect to remove the value.
Here is the code from the Android app that is setting presence in rtdb:
getFirebaseDatabase().goOnline();
DatabaseReference.goOnline();
// Since I can connect from multiple devices, we store each connection instance separately
// any time that connectionsRef's value is null (i.e. has no children) I am offline
final FirebaseDatabase database = getFirebaseDatabase();
final DatabaseReference myConnectionsRef = database.getReference("/auth/" + getFirebaseAuth().getUid() + "/connections");
// Stores the timestamp of my last disconnect (the last time I was seen online)
final DatabaseReference lastOnlineRef = database.getReference("/auth/" + getFirebaseAuth().getUid() + "/lastOnline");
connectedRef = database.getReference(".info/connected");
presenceChangeListener = connectedRef.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot snapshot) {
boolean connected = snapshot.getValue(Boolean.class);
if (connected) {
DatabaseReference con = myConnectionsRef.push();
// When this device disconnects, remove it
con.onDisconnect().removeValue()
.addOnSuccessListener(new OnSuccessListener<Void>() {
#Override
public void onSuccess(Void aVoid) {
// Add this device to my connections list
// this value could contain info about the device or a timestamp too
con.setValue("ANDROID");
}
})
.addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
Log.d(TAG, "### Failed to set onDisconnect ###");
e.printStackTrace();
}
});
// When I disconnect, update the last time I was seen online
lastOnlineRef.onDisconnect().setValue(ServerValue.TIMESTAMP);
}
}
#Override
public void onCancelled(DatabaseError error) {
Log.w(TAG, "Listener was cancelled at .info/connected");
}
});
The problem that I am having is that if the user logs out, the onDisconnect doesn't execute unless I first manually disconnect from rtdb. I'm assuming that the code running on the Real-Time DB gets a permission denied since the auth is no longer valid.
//If I don't go offline first the record in rtdb will not be removed.
DatabaseReference.goOffline();
AuthUI.getInstance().signOut(this)
.addOnCompleteListener(new OnCompleteListener<Void>() {
public void onComplete(#NonNull Task<Void> task) {
// user is now signed out
Log.d(TAG, "Logged out");
application.clearData();
DatabaseReference.goOffline(); //This doesn't cause a presence update here
finish();
}
});
Above is the work-around I'm using, first telling the database to goOffline then to logout. If the user ever gets logged out by another means (the web app is seeing if multiple tabs are using the app and one logs out) the user will be left with a connection not removed.
If I don't call the goOffline() prior to logout, the connection in rtdb will not be removed, even if I force close the application.
I have also verified that I can get everything working fine if I change my rtdb rules to be ".write": "true" <-which is no good. This tells me that there is a permission denied with the onDisconnect running when a user logs out of the auth.
I would like my real-time rules to be something like this.
{
"rules": {
"auth": {
"$uid": {
".read": "auth != null && auth.uid == $uid",
".write": "auth != null && auth.uid == $uid"
}
}
}
}
I would have hoped that the onDisconnect would still be able to execute with the auth of the user when the onDisconnect was setup.
When you attach a onDisconnect() handler, you're registering a delayed write on the Firebase servers. Whether that write is allowed is checked both when you attach the handler, and when the handler is triggered. And since your user is signed out when the write is triggered, it get rejected by your rules. There is no configuration option to change this behavior, so you'll have to come up with a different approach.
So, because 1.) the onDisconnect() execution is evaluated against the rules of the RTDB, 2.) the user who setup the onDisconnect() may lose authentication, and 3.) I would like to make the presence system secure for my auth'ed users... I came up with the following solution:
First, write the presence entries to the RTDB under a path that contains both the user's authId and a UUID to make the location "unguessable".
"/presence/" + {auth-uid} + "/connections/" + {UUID}
and setup a .onDisconnect() to remove this value stored at the unguessable location.
Then, setup the RTDB rules to do the following:
do not allow any reading of the presence data
allow users to add/modify data only under their auth directory
allow any user to delete records (they would need to know the unguessable path)
"presence": {
".read": "false",
".write": "false",
"$auth_id": {
"connections": {
"$uuid": {
".write": "(newData.exists() && $auth_id === auth.uid) || !newData.exists()"
}
}
}
}
Finally, setup a trigger function on the RTDB to read the .ref('/presence/{authid}') location and push the user's presence to another user accessible location (I'm pushing it to my Firestore DB). Also, if the user is changing from "online" to "offline" update a lastOnline timestamp to the current time.
This seems like the best solution given my requirements of having reliable and secure presence system. I hope this helps others.
It is an old question but it made me think about a possible solution and came with the following...
use onDisconnect.setValue/removeValue as long as you have control/awareness over the application
use onDisconnect.cancel and delete to data before logging out
I took #FrankvanPuffelen code and modified it but didn't tested it so....
//getFirebaseDatabase().goOnline();
//DatabaseReference.goOnline();
// Since I can connect from multiple devices, we store each connection instance separately
// any time that connectionsRef's value is null (i.e. has no children) I am offline
final FirebaseDatabase database = getFirebaseDatabase();
final DatabaseReference myConnectionsRef = database.getReference("/auth/" + getFirebaseAuth().getUid() + "/connections");
// Stores the timestamp of my last disconnect (the last time I was seen online)
final DatabaseReference lastOnlineRef = database.getReference("/auth/" + getFirebaseAuth().getUid() + "/lastOnline");
connectedRef = database.getReference(".info/connected");
presenceChangeListener = connectedRef.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot snapshot) {
boolean connected = snapshot.getValue(Boolean.class);
if (connected) {
// simple solution to reuse the old unique key-name otherwise current solution is like performing new registration of a new client over and over on the same client. we should use the old unique key-name until logout is performed
String keyName = SharedPrefUtil.INSTANCE.getFirebaseConnectionKeyName(context);
DatabaseReference con;
if (TextUtils.isEmpty(keyName)) {
con = myConnectionsRef.push();
SharedPrefUtil.INSTANCE.setFirebaseConnectionKeyName(context.getApplicationContext(), con.getKey());
}else{
con = myConnectionsRef.child(keyName);
}
// When this device disconnects, remove it
con.onDisconnect().removeValue()
.addOnSuccessListener(new OnSuccessListener<Void>() {
#Override
public void onSuccess(Void aVoid) {
// Add this device to my connections list
// this value could contain info about the device or a timestamp too
con.setValue("ANDROID");
}
})
.addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
Log.d(TAG, "### Failed to set onDisconnect ###");
e.printStackTrace();
}
});
// When I disconnect, update the last time I was seen online
lastOnlineRef.onDisconnect().setValue(ServerValue.TIMESTAMP);
}
}
#Override
public void onCancelled(DatabaseError error) {
Log.w(TAG, "Listener was cancelled at .info/connected");
}
});
in the logout method we need to cancel the disconnect
String keyName = SharedPrefUtil.INSTANCE.getFirebaseConnectionKeyName(context);
if (!TextUtils.isEmpty(keyName)) {
final FirebaseDatabase database = getFirebaseDatabase();
final DatabaseReference myConnectionsRef = database.getReference("/auth/" + getFirebaseAuth().getUid() + "/connections/" + keyName);
// Stores the timestamp of my last disconnect (the last time I was seen online)
final DatabaseReference lastOnlineRef = database.getReference("/auth/" + getFirebaseAuth().getUid() + "/lastOnline");
// This client/user doesn't need the disconnect functionality
myConnectionsRef.onDisconnect().cancel();
// now we are on our own so we need to remove the key-name from the rmdb
myConnectionsRef.setValue(null);
// remove the key-name from the preferences so we will create a new one in the next login session
SharedPrefUtil.INSTANCE.removeFirebaseConnectionKeyName(context);
// we will not forget to disconnect last time updates
lastOnlineRef.onDisconnect().cancel()
}
AuthUI.getInstance().signOut(this)
I didn't tested it and it will not run as it is missing the SharedPrefUtil implementation

A new Firebase "ListenerForSingleValueEvent" stops working

I'm experimenting with Firebase for Android and wrote a login system.
My app allows the user to register and log in with a username.
There are times when neither onDataChange or onCancelled gets called.
I searched for documentation and tried out the goOnline() method on the database
before requesting a value but it doesn't solve the problem
I also checked the connection status and saw that my app is disconnected, how can i connect my app again to firebase to check if the username already exists?
This is not the only singleEventListener that doesn't work after a while, this happens for all of them
When testing the app after the first install, there's no problem. It starts from the second time when i open the app after a while, that the listeners stop working for that session and all app sessions after that
//check if username is available
mDatabase.goOnline();
mDatabase.child("usernameToEmail").child(mUsernameView.getText().toString())
.addListenerForSingleValueEvent(new ValueEventListener(){
#Override
public void onDataChange(DataSnapshot snapshot)
{
if (snapshot.exists())
{
showProgress(false);
mUsernameView.setError(getString(R.string.error_username_taken));
mUsernameView.requestFocus();
showKeyboard();
return;
}
else
{
register(email,username,password);
}
}
#Override
public void onCancelled(DatabaseError databaseError)
{
//do nothing
showProgress(false);
Toast.makeText(RegistrationActivity.this, databaseError.getMessage(), Toast.LENGTH_SHORT).show();
return;
}
});

Firebase database OnDataChanged is not called in Android app

I realize similar questions have been asked here, but previous solutions do not seem to apply. I'm developing an android app using Firebase auth, and I'm trying to keep track of the user handles that are registered by means of a Firebase database. This is very similar to Tanishq Sharma's answer to firebase duplicate username and custom sign-in.
My problem is that the onDataChange method of the ValueEventListener is never called. As suggested by Frank van Puffelen in Firebase with java (non-android) retrive information, I tried to add some wait time, but this did not solve the problem. After the wait time has passed, the onCancelled method is always called.
The code below shows the function that should return true if a user handle is listed in the Firebase database at node "takenHandles". I know that the connection to the database is working, because I'm able to add a new handle to the database by another part of my code without any issues. Any suggestions on what could be causing this problem?
EDIT It seems I don't have permission to comment on my own question. Thanks Frank, for your quick reply! That indeed explains why writing to the database works, because I do that just after a new user has been authenticated. I should probably consider changing the database rules.
public boolean handleTaken(final String mHandle) {
final boolean[] isTaken = new boolean[]{true};
FirebaseDatabase database = FirebaseDatabase.getInstance();
DatabaseReference takenHandles = database.getReference("takenHandles");
takenHandles.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
Log.d(TAG, "ValueEventListener: onDataChange");
isTaken[0] = dataSnapshot.hasChild(mHandle);
}
#Override
public void onCancelled(DatabaseError databaseError) {
Log.d(TAG, "ValueEventListener: onCancelled");
Toast.makeText(MyAccountActivity.this, "Connection error, please try again", Toast.LENGTH_SHORT).show();
}
});
try {
Thread.sleep(20000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return isTaken[0];
}

Categories

Resources