Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is FirebaseAuth ID token and is the getIdToken method for it thread safe?

I have been using FirebaseAuth to sign in a user and have noticed that the FirebaseUser's getToken method returned ID token is different from the FCM FirebaseInstanceIdService.

What exactly is the difference between the FirebaseAuth ID token and the FCM instance id onTokenRefresh returned token? Instance ID token and ID token are similarly named and thus seem a bit confusing to me.

From my observation, the FirebaseAuth tokens obtained via getIdToken method on the FirebaseUser object expires in an Hour.

getIdToken(boolean forceRefresh)

Eg for the token is...

 01-18 17:20:08.904 15947-15947/? D/FragmentCreate: Token found without force refresh from a single thread eyJhbGciOiJSUzI1NiIsImtpZCI6ImExNTAxNjY5NTNiYTFhMjBjY2FhOTdmOTM4M2NiMDg3OTYyODBkZDcifQ.eyJpc3MiOiJodHR....JTXpA

I have observed that setting forceRefresh to true changes the token immediately.

Eg.

01-18 17:22:16.990 15947-15947/? D/FragmentCreate: Token found single thread after force refresh eyJhbGciOiJSUzI1NiIsImtpZCI6ImExNTAxNjY5NTNiYTFhMjBjY2FhOTdmOTM4M2NiMDg3OTYyODBkZDcifQ.eyJpc3MiOiJod.......xQCA

Now when I call getIdToken(false) after an hour of the last shown forced refresh, the token does change as it has expired and from a little digging into the firebase code, I could see some check like isValid() which I am guessing checks the token expiry and refreshes it even if force refresh is false.

To make things interesting, I called the getIdToken(false) from two threads simultaneously a little after the token expiry to see if both print different tokens as both will see that the token has expired at the same time and thus both should try refresh the token

Eg.

01-18 18:25:29.479 29849-29849/com.foodiniq.waitlist.katana D/FragmentCreate: Token found from thread1 after expiry eyJhbGciOiJSUzI1NiIsImtpZCI6ImExNTAxNjY5NTNiYTFhMjBjY2FhOTdmOTM4M2NiMDg3OTYyODBkZDcifQ.eyJpc3MiOiJod........GpdbA


01-18 18:25:30.071 29849-29849/com.foodiniq.waitlist.katana D/FragmentCreate: Token found from thread1 after expiry eyJhbGciOiJSUzI1NiIsImtpZCI6ImExNTAxNjY5NTNiYTFhMjBjY2FhOTdmOTM4M2NiMDg3OTYyODBkZDcifQ.eyJpc3MiOiJod........GpdbA

To my surprise, the token indeed was refreshed as i predicted but both of them gave the same result. This kind of hints that the method used to get the token internally might be synchronized and would return the same result at least when called with force refresh set to false from two different threads. For forceRefresh set to true also showed similar result with simultaneous threads printing a new token with same value

Am i correct in assuming that the getIdToken(false) method is thread safe and will always return only the same value in all threads when called simultaneously? Will this behaviour be different from getIdToken(true)?

P.S the code used to get the token without refresh from two threads is

                        Thread testThread1 = new Thread(new Runnable() {
                        @Override
                        public void run() {

                            if(FirebaseAuth.getInstance().getCurrentUser()!=null){

                                FirebaseAuth.getInstance().getCurrentUser().getIdToken(false).addOnCompleteListener(new OnCompleteListener<GetTokenResult>() {
                                    @Override
                                    public void onComplete(@NonNull Task<GetTokenResult> task) {

                                        if (task.isSuccessful()) {
                                            Log.d("FragmentCreate","Token found from thread1 after expiry "+task.getResult().getToken());
                                        }

                                    }
                                }).addOnFailureListener(new OnFailureListener() {
                                    @Override
                                    public void onFailure(@NonNull Exception e) {

                                        Log.d("FragmentCreate","Token failed from main thread single "+e.toString());

                                    }
                                });

                            }

                        }
                    });

                    Thread testThread2 = new Thread(new Runnable() {
                        @Override
                        public void run() {

                            if(FirebaseAuth.getInstance().getCurrentUser()!=null){

                                FirebaseAuth.getInstance().getCurrentUser().getIdToken(false).addOnCompleteListener(new OnCompleteListener<GetTokenResult>() {
                                    @Override
                                    public void onComplete(@NonNull Task<GetTokenResult> task) {

                                        if (task.isSuccessful()) {
                                            Log.d("FragmentCreate","Token found from thread2 after expiry "+task.getResult().getToken());
                                        }

                                    }
                                }).addOnFailureListener(new OnFailureListener() {
                                    @Override
                                    public void onFailure(@NonNull Exception e) {

                                        Log.d("FragmentCreate","Token failed from main thread single "+e.toString());

                                    }
                                });

                            }

                        }
                    });

                    testThread1.start();

                    testThread2.start();

Method to call token with force refresh from one thread is:

        if(FirebaseAuth.getInstance().getCurrentUser()!=null){

                        FirebaseAuth.getInstance().getCurrentUser().getIdToken(true).addOnCompleteListener(new OnCompleteListener<GetTokenResult>() {
                            @Override
                            public void onComplete(@NonNull Task<GetTokenResult> task) {

                                if (task.isSuccessful()) {
                                    Log.d("FragmentCreate","Token found single thread after force refresh "+task.getResult().getToken());
                                }

                            }
                        }).addOnFailureListener(new OnFailureListener() {
                            @Override
                            public void onFailure(@NonNull Exception e) {

                                Log.d("FragmentCreate","Token failed from main thread single "+e.toString());

                            }
                        });

                    }

I want a thread safe implementation due to the following reason:

On every app launch, I'm fetching a token without forcing a refresh. And using this token in all subsequent requests.( Thus there can be a point where this token could've expired, however unlikely the case that the user kept the app running for an hour )

What I'm doing on the backend is as you said verifying the id token. Here there can be a point where the token could've expired. So I'm sending a response code which tells the client to refresh the token manually. Now this is being done on all the Servlets on the backend so a number of them can return the expired response code at the same time. On this response, I'm refreshing the token on client from any thread which got the response code,thus leading to a number of different threads calling refresh method on client. I'm basically worried that the might be a quota in the refreshes of the token

like image 381
Kushan Avatar asked Jan 18 '18 13:01

Kushan


1 Answers

Firebase ID token and FCM token are two completely different things: the first is an authentication token, needed to autenticate a request on your backend server, the latter is to identify uniquely an instance of an app installation, to know to whom send the proper message. Consider also the Firebase user authentication ID - which is thread safe and unique id for a certain user account.

From official doc

Verify ID Tokens
If your Firebase client app communicates with a custom backend server, you might need to identify the currently signed-in user on that server. To do so securely, after a successful sign-in, send the user's ID token to your server using HTTPS. Then, on the server, verify the integrity and authenticity of the ID token and retrieve the uid from it. You can use the uid transmitted in this way to securely identify the currently signed-in user on your server.

while (from official doc):

Access the device registration token
On initial startup of your app, the FCM SDK generates a registration token for the client app instance. If you want to target single devices or create device groups, you'll need to access this token by extending FirebaseInstanceIdService.

This section describes how to retrieve the token and how to monitor changes to the token. Because the token could be rotated after initial startup, you are strongly recommended to retrieve the latest updated registration token.

The registration token may change when:

  • The app deletes Instance ID
  • The app is restored on a new device
  • The user uninstalls/reinstall the app
  • The user clears app data.

The point thus seems to be slightly off-focus: why do you want to have a thread-safe and unique auth token? On your backend server you can simply get the uid of the user by calling a proper function (e.g. Node.js code):

admin.auth().verifyIdToken(idToken)
  .then(function(decodedToken) {
    var uid = decodedToken.uid;
    // ...
  }).catch(function(error) {
    // Handle error
  });

Feel free to elaborate/improve the question if these info change the understanding of what happens in your code.

like image 85
Fabio Veronese Avatar answered Oct 25 '22 17:10

Fabio Veronese