Put an end to token expiration madness

973 views
Skip to first unread message

ionut stoica

unread,
Mar 10, 2015, 3:30:17 PM3/10/15
to fireba...@googlegroups.com
Although there are several other means of authentication, I have chosen the custom token authentication as it makes the most sense.
I have "server" like machines that gather data from some sensors in a building, these machines have isolated datastores where they can write/read, they cannot and should not access other instances scopes.
These machines must be permanently connected but their data access must be isolated, therefore I cannot use any other authentication than this based on custom tokens.
So secret based authentication is eliminated as that one allows an instance to access any scope, it would have been a good choice as it does not have expiration.
I also cannot give years of age to the token, that defeats the purpose of token security.

FIREBASE WARNING: auth() was canceled: Auth token is expired

First attempt

Let us check the docs https://www.firebase.com/docs/web/guide/login/custom.html
There we find this gem:

To handle token expiration gracefully, the authentication function in the client library for each platform (JavaScript, Objetive-C, Java) allows us to set a cancel callback that is triggered when a token expires. The authentication function's success callback will provide authentication info. Using this, we can tell in advance when the token will expire.


Now, how do you set this cancel callback, no problem, let us dig deeper in the docs https://www.firebase.com/docs/web/api/firebase/authwithcustomtoken.html
The documentation shows 3 parameters for this function, the first two are obvious, for the last one I could not find more than:

An object containing optional client arguments, such as configuring session persistence.

ARGH ... I have no idea what it means, happy it exists, what does it do ? there is nothing related to a cancel callback that is triggered when a token expires


Second attempt

Pre-emptive approach, by using the onComplete handler of authWithCustomToken one can detect expiration manually

    scope.authWithCustomToken(token, function(error, authData) {
     
if (error) {
        console
.error('auth error', error, authData)
     
} else {
       
// handle successful authentication
        signals
.authenticated.dispatch(authData)
       
// start a token expiration monitor
       
var nowDate = new Date(),
            expDate
= new Date(authData.expires * 1000),
           
expTimeLeft= Math.floor((expDate.getTime() - (new Date().getTime()))/1000),
       
if (expTimeLeft === 0) {
          console
.log('token has expired - no more time left')
          signals
.tokenExpiring.dispatch()
       
} else {
          console
.log('token will expire in: ' + expTimeLeft + ' seconds')
          setTimeout
(function() {
            console
.log('token has expired')
            signals
.tokenExpiring.dispatch()
         
}, (expTimeLeft <= 0 ? 0 : expTimeLeft) * 1000)
       
}
     
}
   
})


I am using js-signals from http://millermedeiros.github.io/js-signals to dispatch this tokenExpiring event a few moments before the actual expiration happens, the one that triggers the warning.
Although simple, this method looks ugly, a patch, smelly and something that is not to be liked and it comes with additional confusion.
What am I supposed to do after the token expiration has been detected ? I want to continue the life of the server machine without disconnecting it.
Currently, at expiration, I call authWithCustomToken again, but I do not know if it is the right thing to do. This is also cumbersome, because all listeners that are added after authenticated signals is dispatched, must be tracked and cleared.

Questions
  1. Does that authentication function expiration callback in the client library for each platform really exist ? Where is it, how do you use it ?
  2. In case it does not exist, isn't there a way to just replace the token, without performing authentication again ?
  3. How do you guys handle this cleanly ?

P.S. - I am aware-ish of how other proposed to solve this, I did not like anything that I found. If Firebase SDK is able to display "FIREBASE WARNING: auth() was canceled: Auth token is expired." for sure it can be able to trigger a custom function just before or after that, without using the setTimeout that I have, if I could easily tap there.

Jacob Wenger

unread,
Mar 10, 2015, 3:47:47 PM3/10/15
to fireba...@googlegroups.com
Hey there,

Wow, lots of ground to cover here. The tl;dr here is to use onAuth(), but I'll see if I can hit all your key points:

I also cannot give years of age to the token, that defeats the purpose of token security.

I'm not sure I totally agree with this one. If you have a token which is stored on your server and is never sent over the wire, I don't see an issue with given it a practically infinite lifetime. If it is on your server, the only way to get to it is to hack into your server, at which point you are probably toast anyway. Also, if you generate a new secret just for generating these "server" tokens, the token can always be revoked by deleting the secret used to generate it. Also, if you want to add logic to programmatically generate new tokens, you need to store your secret on your server to do so. This is inherently less secure than storing an everlasting "server" token instead.

Does that authentication function expiration callback in the client library for each platform really exist? Where is it, how do you use it?

The paragraph you quoted in our documentation is somewhat out of date. It applied well to the old Simple Login client, but does not really fit into the current Firebase Authentication. Apologies for the confusion. I've already drafted up a fix for the docs and it will go out with our next website deploy.

In case it does not exist, isn't there a way to just replace the token, without performing authentication again?

No, and I don't really understand what this would really mean. If your token expires, you are by definition no longer authenticated. You will need to reauthenticate with a new token. I don't see any workaround to this.

How do you guys handle this cleanly?

Ahhh, here is the meat of the issue. The method you are looking for is onAuth(). It will fire every time authentication state changes. This includes when you first authenticate, when you call unauth(), and when a token expires. So that is what you want. You should put your token expiration logic in your onAuth() callback.

Jacob

--
You received this message because you are subscribed to the Google Groups "Firebase Google Group" group.
To unsubscribe from this group and stop receiving emails from it, send an email to firebase-tal...@googlegroups.com.
To post to this group, send email to fireba...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/firebase-talk/8c29063d-a3bc-482c-854d-ead594105903%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

ionut stoica

unread,
Mar 20, 2015, 1:53:47 PM3/20/15
to fireba...@googlegroups.com

Thank you for the answers.
Although they make some clearing-ups, there is still a lot of confusion for me.
I handle everything in onAuth this time as it seems to be the idiomatic way.
But now, when token expires, the Firebase session is not closed which is unexpected, so onDisconnect is not called for any scope and cleanups are not done.
Why is it still connected ? As you yourself noticed, it does not make sense keeping the connection, but it is kept, and only when I completely kill the connection by stopping the script am I able to see all the onDisonnect beeing called.
Why is Firebase still connected when token expires ? I tried finding a way to disconnect, not possible, Firebase.goOffline() is there, but I need to connect to multiple firebase application in the same time, some disconnected, some not.
How can you really disconnect and start fresh? I expect to remove all listeners and add new ones as soon as onAuth allows it.


var params = { connection_id: 'machine-A', session_id: gen_guid()  }
var client = root.child('connections/' + params.connection_id),
    mailbox = client.child('mailbox'),
    inbox = mailbox.child('inbox'),
    responses = mailbox.child('responses'),
    session = client.child('sessions/' + params.session_id);

session.onDisconnect().remove()


Now, when onAuth gets a token time-out, I would like to just start fresh from first Authentication Request, thing that is currently impossible(for me)
Again, thank you for all the answers. I am seriously biased as I have did a lot of RTMP applications in my past and it had some very healthy concepts of creating always connected apps.
Things like connection management, session management, disconnections, shared memory across clients I expect to find here too.


In the picture bellow, sessions just keep accumulating, onDisconnect never gets called when token expires. There should be just one all the time.

ionut stoica

unread,
Mar 24, 2015, 5:35:02 AM3/24/15
to fireba...@googlegroups.com
I will answer to this myself as it is tricky.
The token expiration SHOULD be pre-emptive, because the moment onAuth receive token expiration,
any disconnect that is called is not able to do its role as there are no permissions anymore.
I do not think there is a better solution for this.
Non global offline should also be supported, one should easily be able to disconnect from a scope (closing/stopping all network communication with firebase, be it long-polling, true websocket, iframes, etc.)


On Tuesday, March 10, 2015 at 8:30:17 PM UTC+1, ionut stoica wrote:

Jacob Wenger

unread,
Mar 24, 2015, 7:14:59 PM3/24/15
to fireba...@googlegroups.com
Hey there,

It is important to distinguish between becoming unauthenticated and becoming disconnected from the Firebase servers. Becoming unauthenticated means that your session has expired and although you can read from and write to Firebase, you do not have permission to do so. Becoming disconnected means that you cannot read from or write to Firebase because your internet connection (or something similar) has dropped. These are not one in the same.

In general, Firebase uses a single websocket to send data back and forth from your client to our servers. This is always open (assuming you have a connection), even if the current client is not authenticated. This is expected behavior, as you can write security rules to allow unauthenticated users to read from and write to your Firebase. If you want to clean up listeners, you need to do this yourself, by calling off() on the appropriate nodes. When a token expires, we will not do this for you, as authentication and connection are (and should be) unique from each other.

As for your specific use case, it's hard for me to understand exactly what issues you are running into since you have written a long post and have not actually provided a repro. If you still have questions, please be brief and direct and provide a repro (Plunker, CodePen, etc.) of your issue.

Jacob

--
You received this message because you are subscribed to the Google Groups "Firebase Google Group" group.
To unsubscribe from this group and stop receiving emails from it, send an email to firebase-tal...@googlegroups.com.
To post to this group, send email to fireba...@googlegroups.com.
Reply all
Reply to author
Forward
0 new messages