This forum is now a read-only archive. All commenting, posting, registration services have been turned off. Those needing community support and/or wanting to ask questions should refer to the Tag/Forum map, and to http://spring.io/questions for a curated list of stackoverflow tags that Pivotal engineers, and the community, monitor.

Facebook 401 and access_token - any cool ideas?

Apr 22nd, 2012, 05:35 AM

Hi,

What's the best approach of refreshing access_token for users in FB?
I know I can check for the 401 exception and handle myself in the conenction flow (e.g - redirect again to FB and start the authorize flow again) , but I was wondering if that the best practice?

Is there any solution for renewing the token everyday ? (now that the offline_access is dead?)

Good question. Unfortunately, I still don't have a good answer for that (but I am working on it).

Facebook's killing off of offline_access is a *GOOD* thing. Their approach is quite a bit closer to what the OAuth 2 spec does with refresh tokens (essentially, using the access token as its own refresh token). But, I still don't have a good answer for you on the *best* way to do this.

Certainly one way to do it (albeit cumbersome) is to directly query the UserConnections table to get the access token and then use RestTemplate to directly do the refresh yourself...followed by updating the UserConnection table, if necessary. That will work...but it's certainly not the best way to do it.

I'm also not certain that I would refresh every day...what if the user hasn't used your application in several weeks? Then you're refreshing for no reason. I'd tend to refresh only on realization that the token you have is no good.

This is a good opportunity for discussion on the topic, though. My thinking is that this would be handled much the same way as refresh tokens, only with a Facebook twist. I'm always open to ideas on how to do this, though.

Comment

Since I'm using the UserConnections to persist the connections, for now I'm just checking for 401 and if not valid, I'm deleting the connection. This is a very *BAD* approach, since this means that the user has to click the connect again my popup appears again.. (but since the app is already authorized, FB returns a new token and the user doesn't have to click anything.)

I thought about doing a "scheduled" refresh as you suggested but this means I'll refresh lots of tokens for no reason..

Can I use the expiretime column? if so, I can schedule a refresh on each expire token. (though I see some records have expiretime and some not..)

Do you think that in the future spring will handle refreshing of tokens?

Comment

Yes, it's slated for Spring Social 1.1.0 M2 to handle automatic refresh of tokens. The Facebook approach throws a wrinkle into this plan, but I'm still thinking it will be covered.

Regarding scheduled refresh of tokens, I found this yesterday at https://developers.facebook.com/road...cess-removal/: "Note: The user must access your application before you're able to get a valid "authorization code" to be able to make the server-side OAuth call again. Apps will not be able to setup a background/cron job that tries to automatically extend the expiration time, because the "authorization code" is short-lived and will have expired."

In short, Facebook doesn't want you to do automatic refresh. The user has to be involved, they say.

The best and most appropriate strategy for refreshing tokens is to do so in response to the realization that the token has expired. Spring Social's OAuth2Operations interface supports this via the refreshAccess() method. The refreshAccess() method is called from the refresh() method on the connection...so if you can get the connection object, you can call refresh() upon finding out that the token has expired.

This, of course, is based on the OAuth 2 spec and not on Facebook's refresh approach--so it doesn't help you much right now. Fortunately (or unfortunately, depending on your perspective), I've already had to create a special subclass of OAuth2Template for Facebook's quirks, so it looks like I might have to override the refreshAccess() method in FacebookOAuth2Template to deal with this problem, too.

In short, I am now led to believe that this new strategy is *nothing* like OAuth 2 refresh tokens and probably much better described as offline_access with a 60-day expiration.

The key difference between this strategy and refresh tokens is that with Facebook you may only reset the time of a token that has not already expired; with OAuth 2 you can refresh an expired token. Thus, my initial thinking of how to resolve this is probably not good because by the time you recognize that the token has expired, it's too late to "freshen" it.

At the same time, they don't want you to keep freshening it on a schedule, so portions of that document indicate that you should freshen it when the user accesses your app. I guess, upon initial signin or some other "startup" case, the token is refreshed. They didn't make it clear what was meant there, though....and if care is not taken, then you could end up (in an extreme case) attempting to refresh upon every interaction with Facebook (not desirable).

All that said, I'm eager to hear the answers to my questions and willing to hear any other interpretation you may have of the document.

Comment

I can understand your confusion.. I'm reading the docs and sometimes have no idea what the mean (almost as it seems like FB developers only worked @ FB and no other place)

From what I understand from the doc we need to call fb once a day per user (" first time requested") and request a long-lived token. the only question is how to get the expiration time. (Does FB send an expiration date for the tokens? )

my confusion is that they state the short-lived in valid for 1-2 hours - but from my production I've seen that the tokens are valid to ~10 days or so (which is consisted with some jira issues I saw in spring-social)

Actually, your idea with the "startup" is a nice one. doesn't solve all the problems but it's a start for sure.

As for spring api - any chance of tuning the API to return exceptions on the test() (like 401 or something similar) so we'd know when to refresh? right not I'm doing an educated guess...

Comment

Well, the docs say (as far as I can decypher them) that the server-side tokens will "have the longer expiration time by default" and that if a call is made while the long-lived token is still valid, its expiration will be set to a long expiration time. (So, it's already long, but if you make the call then it will be set to be long; I can only assume that they mean that the expiration time will be reset to a new 60-day token, but it doesn't explicitly say that under the server-side section--it's also not clear what "call" they're referring to, because in that context I could interpret it as the initial exchange for an access token or the new API call to extend a token's life.)

But you're right, the docs also say that the initial token is a 2-hour token, but in some of my early tests I was getting token that was good for about 9.5 days.

From what I understand, what they want you to do is extend the life of the token only once per day, but not on a schedule...they want you to do it when the user accesses your app. They explicitly say that you shouldn't do this in a cron job, so the scheduled approach is out...but their reasons for not doing it on a schedule are confusing, because they reference the authorization code which you should no longer have after the initial authorization.

Again, the documentation is a mess and I think that they should clear it up before pushing this on everyone. The semi-good news is that they've delayed offline_access deprecation until July 5, but on May 2nd they're still going to automatically set all applications to have it deprecated; you'll have to set it back if your app breaks. So, whether I want them to or not, they're going to break apps on May 2 and I have to take action to put it back the way it was; sounds like a great way to anger developers. :-)

I'm not likely going to change the test() method to throw an exception...that'd be a breaking change for anyone counting on the current implementation of test(). Right now if it returns false, you may assume that the token you have is no good and try to get another one. But if you want an exception, you can do the equivalent...just fetch the user profile and if the token has expired or is otherwise no good, you'll get the exception that test() would throw if it didn't catch it and convert to a boolean.