How to force logout firebase auth user from app remotely
Asked Answered
M

6

23

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 ?

Minnesinger answered 31/10, 2018 at 16:19 Comment(0)
I
25

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()"
Integrate answered 9/11, 2018 at 22:22 Comment(5)
Thanks, @frank-van-puffelen. In our case, we tried disabling the user and tried revoking his/her refresh token, but the user is still able to access data even after waiting for 1 hour (we waited for 2 days also :) ). We also have rule added in Firestore to allow only authenticated user. If my understanding is wrong, please help me understand when to revoke user refresh token?Rienzi
I noticed I said ID token, where I mean access token, so I fixed that. Revoking access tokens isn't enough, so that's where security rules come in. If the steps don't work for you, show exactly what you've done. Without code or exact steps-to-reproduce, it is hard for anyone to say where you made a mistake.Integrate
@FrankvanPuffelen How long does it take for a user to be logged out once his user is disabled in Firebase Auth console? Is there any docs on this?Goose
"if you disable the user's account, they may continue to have access for up to an hour"Integrate
This is a silly question, but how are you actually defining banned_uuids in firebase rules DSL? Just copy pasting as is doesn't work 😢Cacology
M
9

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!

Melise answered 10/11, 2018 at 6:27 Comment(3)
How can we achieve this with nodejs sdk also i am not using firebase dbArmidaarmiger
Just save the token and send the data with the api firebase.google.com/docs/cloud-messaging/…Melise
I wonder does it work for the case when Android App is running in a background mode?Nodababus
R
3

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

Rutger answered 10/11, 2018 at 15:28 Comment(1)
As far as I know, onNewToken above belongs to FCM. Can you please explain why are we Logging the user out on refresh of FCM token ?Minnesinger
J
2

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();
}
Jemie answered 28/3, 2020 at 5:5 Comment(0)
S
1

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()
                }
            }
        }
    }
}
Shayneshays answered 21/4, 2020 at 8:58 Comment(0)
S
0

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.

Soonsooner answered 15/11, 2018 at 15:53 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.