handling token expiration gracefully

1,908 views
Skip to first unread message

Joshua Haas

unread,
Jun 7, 2013, 4:56:49 PM6/7/13
to fireba...@googlegroups.com
I have a couple questions about token expiration (using custom auth):

1. If the user logs out, is there a way to tell firebase that their token should no longer be valid?  It looks like you can auth, unauth, and then auth again using the same token.  It seems like this is a potential security hole to not be able to cancel a token on log out.


2. Is there a way to smoothly recover from token expiration?  Right now my auth code looks something like this (in coffeescript):


do_auth = ->
   token = get_fresh_token()
   my_base.auth token, do_some_stuff, ->
      do_auth()

Ie, on expiration, I automatically get a fresh token and auth again.  This part works great, but unfortunately it interrupts all my .on() calls.   Is there an easy way to get them to persist past token expiration?  I'd want them to transparently resume updating with fresh data as soon as the token is refreshed.  I suppose I could do some pattern like:

create_on = (base, event, cb) -> 
   base.on event, cb
   add_to_list_to_be_recreated_on_reauth base, event, cb


...but that seems a little clunky, plus I can see that leading to issues with 'child_added' since I think that would reload all the initial data again.

Is there a nicer pattern for handling this?



Here's some test code I was playing with to confirm that token expiration breaks .on() handlers.  

Firebase = require 'firebase'
TokenGenerator = require 'firebase-token-generator'
generator = new TokenGenerator firebase_secret

test_node = new Firebase firebase_url

#Creates a token that expires in 10 seconds
make_token = -> generator.createToken {}, {admin: true, expires: new Date().valueOf() / 1000 + 10}


#Authenticates, calls cb when done, and then re-authenticates on token expiration
do_auth = (cb) ->
    my_base.auth make_token(), (err) ->
        if err
            console.log err
        else
            console.log 'auth succeed'
            cb?()
        
    , (exp) ->
        console.log 'expired: ' + exp
        do_auth()
        
   
#Authenticate, then count off the seconds
do_auth ->
    test_node.on 'value', (snap) ->
        console.log 'got a value: ' + snap.val()
    , (exp) ->
        #For some reason this code never seems to run!!
        console.log 'lost permission: ' + exp
    for i in [1..60]
        do (i) ->
            setTimeout (-> test_node.set i), i * 1000


The output of this code is:

auth succeed
got a value: 60  #my initial value from last time
got a value: 1
got a value: 2
got a value: 3
got a value: 4
got a value: 5
got a value: 6
got a value: 7
got a value: 8
FIREBASE WARNING: on() or once() for /test_node failed: permission_denied 
lost permission: undefined
FIREBASE WARNING: auth() was canceled: Auth token is expired. 
expired: Error: Auth token is expired.
auth succeed
FIREBASE WARNING: auth() was canceled: Auth token is expired. 
expired: Error: Auth token is expired.
auth succeed
#...and so on to infinity... we never see numbers past 8.
        
        

Michael "Kato" Wulf

unread,
Jun 7, 2013, 6:17:54 PM6/7/13
to fireba...@googlegroups.com
Hi Joshua,

1) Any token or security related data stored on the client is a potential security risk. Striking the proper balance between security and convenience is a task that varies according to each application and environment. There are valid use cases for keeping a token around and using it to re-authenticate in the future. For example, storing the token in a server session and re-authenticating between pages, or a client-only app where we do not want to force the client to authenticate any time the page is refreshed or a link is navigated.

In an app where security is valued above convenience, and each page load should require establishing a new token, the best way to prevent the token from being utilized would be to never store it on the client. The method that receives the token could simply pass it directly into the auth() call and never store it in a global variable, cookie, local storage, or any place that it could be retrieved and accidentally used later.

2) I've run into the same difficulty with my on() routines. Rather than trying to solve this on loss of authentication, I found it easier to simply see when the token expires and then re-authenticate before the connection is lost. Michael Lehenbauer offered me this wonderful nugget to preemptively re-authenticate.

For one use case, I actually simplified that process. The app used a 24 hour expiration. If someone tried to log in with a token that expired in the next 12 hours, then the token was discarded and a new one had to be obtained, ensuring at least 12 hours without expiration. 

If the user  stay logged in more than 12 hours, or some other unlikely event forces expiration (e.g. a server restart) then I just told the user they had been logged out and reloaded the page after confirmation. It turned out to be a pretty decent compromise that was well accepted.


Hope that gives you some ideas!

Cheers,
Kato





--
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.
For more options, visit https://groups.google.com/groups/opt_out.
 
 



--
Michael Wulf
www.wordspot.org


Joshua Haas

unread,
Jun 8, 2013, 9:57:12 AM6/8/13
to fireba...@googlegroups.com
Thanks for your answers!

I'm not a huge security expert but it seems like there should always be a way of forcing a logout... for instance, if someone's account is compromised and we need to reset it, it would be bad if there was absolutely nothing we could do for the duration of the token.  I'm pretty sure most services enable some way of immediately logging out.

I guess we could deal with it by having short-lived tokens, a prospect that's a lot more appealing now that I know how to preemptively refresh tokens -- thanks for passing on Michael's code snippet!

Andrew Lee

unread,
Jun 8, 2013, 1:10:38 PM6/8/13
to fireba...@googlegroups.com
Hi Joshua -

That's actually pretty easy to do without expiring the token. You could simply have a "version number" as part of the data in the token, and then have a security rule requiring that the version number match some number already in Firebase. When you want to boot someone, you change their version number and all expiring token will instantly be invalid.

Remember that the authentication tokens are just a way of signing a payload and sending data to our servers. They don't actually grant any permissions -- that's what the security rules are for.

-Andrew
Andrew Lee
Founder, Firebase
http://twitter.com/startupandrew

Joshua Haas

unread,
Jun 8, 2013, 1:26:30 PM6/8/13
to fireba...@googlegroups.com
Thanks!  That's a great solution, I wasn't thinking about it from that angle.
Reply all
Reply to author
Forward
0 new messages