18
votes

I have a project which uses firebase auth with firebaseUI to authenticate users. I have enabled Google, Facebook and email providers. What I need is to remotely logout or disable some of the users.

I want the users to logout from the app on doing so. I tried disabling the user in the firebase console and also used the firebase admin SDK (https://firebase.google.com/docs/auth/admin/manage-sessions) to revoke the refresh tokens.

I waited for more than 2 days and still noticed that the user was logged in and could access the firestore data.

I have also gone through and tried Firebase still retrieving authData after deletion

Can anyone point to what I am doing wrong ?

6

6 Answers

17
votes

You also cannot remotely force a user to be signed out. Any sign out will have to happen from the device that the user is signed in on.

There is no way to revoke an access token once that is minted. This means that even if you disable the user's account, they may continue to have access for up to an hour.

If that is too long, the trick (as also mentioned in my answer to the question you linked) is to maintain a list of blocked users in your database (or elsewhere) and then check against that in your security rules (or other authorization layer).

For example in the realtime database, you could create a list of blocked user's UIDs:

banned_uids: {
  "uid1": true
  "uid2": true
}

And then check against that in your security rules with:

".read": "auth.uid !== null && !root.child('banned_uids').child(auth.uid).exists()"
9
votes

You can send a message data with FCM to force to log out.

For example, if the users use android application.

  1. Save the FCM token in a collection in firebase Realtime.
  2. configure the Android client app, in the service. LINK You have to make when receive a message with especial string, force to log out.
  3. make the trigger you need in cloud functions, to send the data LINK when you need the user log out.

SUCCESS!

3
votes

As per your scenarios, i assume that you need to make user logout when user is disabled.

Use One global variable to store TokenNo (might be in shared preference or sqlite):

Add following code to your manifest:

<service android:name=".YourFirebaseMessagingService">
 <intent-filter>
     <action android:name="com.google.firebase.MESSAGING_EVENT" />
 </intent-filter>
</service>

Add following code in your

public class LogoutOntokenchange extends FirebaseMessagingService{
   @Override
   public void onNewToken (String token){
     if(TokenNo=>1){ //if tokenNo >=1 means he already logged in
       TokenNo=0;
       FirebaseAuth.getInstance().signOut(); //Then call signout method
     }
     else{
       TokenNo=1; //store token no in db
     }
   }
}

What Happens here:
When user logged in first time onNewToken is called then It goes into else then TokenNo is updated to 1 from 0.
When You disable any user then automatically token is refreshed.Then OnNewToken is called then TokenNo>=1 so user will be logged out.

NOTE: When user log in for first time i.e if TokenNo variable is not stored then store it as 0.

For reference: https://firebase.google.com/docs/reference/android/com/google/firebase/messaging/FirebaseMessagingService

1
votes

What I've done is I created for each user upon registration a Firestore document with the UID as document ID. In this document I store an array which stores all fcm tokens the individual user receives when logging into a new device. That way I always keep track where the user is logged in. When the user logs out manually the fcm token will be deleted from the document in Firestore as well as on the device.

In order to be able to log out the user everywhere they are signed in I did the following. When starting the app and once the user is logged in I start a snapshot listener that listens to all changes in the users document. As soon as there is a change I retrieve the new array of fcm tokens, search inside the array for the local current device fcm token. If found, I do nothing. If the fcm token is no longer in the array I will call the local logout method and go back to the login screen.

Here are the methods I used in swift on iOS. The closures (passOnMethod) will just trigger an unwind segue to the login view controller.

import Foundation
import Firebase

class FB_Auth_Methods {

let db = Firestore.firestore()
var listener: ListenerRegistration?

func trackLoginStatus(passOnMethod: @escaping () -> () ) {
    listener?.remove()
    if let loggedInUserA_UID = Auth.auth().currentUser?.uid {
        listener = db.collection(K.FStore.collectionOf_RegisteredUsers_Name)
            .document(loggedInUserA_UID)
            .addSnapshotListener { (snapshotDocument, error) in
                if let error = error {
                    print(error)
                } else {
                    if let document = snapshotDocument {
                        if let data = document.data() {
                            if let fcmTokens = data[K.FStore.Users.fcmTokens] as? [String] {
                                print("Found the following tokens: \(fcmTokens)")
                                self.compareTokensAgainstCurrentDeviceToken(fcmTokens: fcmTokens, passOnMethod: { () in
                                    passOnMethod()
                                })
                            }
                        }
                    }
                }
        }
    }
}

func compareTokensAgainstCurrentDeviceToken(fcmTokens: [String], passOnMethod: @escaping () -> () ) {
    InstanceID.instanceID().instanceID { (result, error) in
        if let error = error {
            print(error)
        } else if let result = result {
            if fcmTokens.contains(result.token) {
                print("Token found, doing nothing")
            } else {
                print("Token no longer found, logout user")
                do {
                    try Auth.auth().signOut()
                    InstanceID.instanceID().deleteID { error in
                        if let error = error {
                            print(error)
                        } else {
                            passOnMethod()
                        }


                    }
                    } catch let signOutError as NSError {
                        print (signOutError)
                    }
                }
            }
        }
    }
}

And here is the method I use when logging out the user everywhere but at the current device.

func deleteAllFcmTokensExceptCurrent(loggedInUserA: User, passOnMethod: @escaping () -> () )  {

    InstanceID.instanceID().instanceID { (result, error) in
        if let error = error {
            print(error)
        } else if let result = result {
            let batch = self.db.batch()

            let deleteAllFcmRef = self.db.collection(K.FStore.collectionOf_RegisteredUsers_Name).document(loggedInUserA.uid)
            batch.updateData([K.FStore.Users.fcmTokens: FieldValue.delete()], forDocument: deleteAllFcmRef)

            let updateFcmTokenRef = self.db.collection(K.FStore.collectionOf_RegisteredUsers_Name).document(loggedInUserA.uid)
            batch.updateData([K.FStore.Users.fcmTokens: FieldValue.arrayUnion([result.token])], forDocument: updateFcmTokenRef)

            batch.commit { (error) in
                if let error = error {
                    print(error)
                } else {
                    passOnMethod()
                }
            }
        }
    }
}
0
votes

Not tested yet, as our backend programmer, who is in charge of setting up Firestore rules was gone for the day, but in theory this should work: (and it's something I'll test tomorrow)

Having a FirebaseAuth.AuthStateListener in charge of serving UI based on the status of the user

This combined with rules in firestore

match /collection
allow read: if isAuth();

Where isAuth is:

function isAuth() {
  return request.auth.uid != null;
}

If the user is then disabled, while being logged in, whenever the user tries to read data from the collection, he should be denied, and a signOut() call should be made. The AuthStateListener will then detect it, and sign the user out.

0
votes

The only way I can think about is adding a if-else block in your starting activity. Store the that status of user (verified/banned/deleted) in Firebase Real-time database. Then retrieve the status of user at start of application and add the code:

if (currentUserStatus.equals("banned"))
{
currentUser.logout();
}