Skip to content
This repository has been archived by the owner on Apr 5, 2022. It is now read-only.

Support for new Facebook endpoint to extend access token lifetime #57

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

JohnWPhillips
Copy link

Facebook has begun supporting an OAuth2 extension to allow a server side request to exchange a short-lived Access Token for a Long-lived Access Token. I've added support for this into the spring-social OAuth2Operations interface and the spring-social-facebook FacebookOAuth2Template, please consider this for addition into a future spring-social release.

Since this is Facebook-specific an alternate implementation would be to subclass the OAuth2Operations interface in the spring-social-facebook package and have the user upcast the object from connectionFactory.getOAuthOperations(). This would be better because it would not add a Facebook specific API to the base class interface, however, it may be possible that what Facebook has done will somehow become part of a future OAuth standard and so it will need to be added to the base API at some point anyway.

Certainly interested in any insights SpringSource has into Facebook's deprecation of offline_access mode. Feel free to contact me at [email protected] for more info about my submitted changes.

…ific extension which allows the user to request a long-lived access token given that he has

a short-lived one
@habuma
Copy link
Contributor

habuma commented May 11, 2012

See my comments on the other pull request you submitted.

Also, I already have a similar piece of code in place in a local branch that does not require any changes to Spring Social Core. But as I said in the other pull request, the endpoint can only be used for client-side extension of token expiration.

As for this someday becoming part of the OAuth standard, I have serious doubts. The OAuth 2 spec isn't final yet, but it also hasn't changed dramatically since around draft 14 and I believe it is inching closer to being final. Adding something like this would no doubt disrupt that specification process, further prolonging finalization of the spec.

Furthermore, the OAuth 2 specification already defines the notion of refresh tokens which solve this problem in a cleaner way than what Facebook has come up with. It's unfortunate that Facebook is not implementing the refresh token portion of the specification that Facebook itself is helping to write. I suspect that the odds are greater that Facebook will implement refresh tokens before the OAuth 2 spec will define Facebook's approach. But time will tell...until then, I'm not eager to add anything to Spring Social Core to directly support anything provider specific such as this.

@JohnWPhillips
Copy link
Author

Thanks for the input, I appreciate it. My interpretation of what's going
on is based on how mobile app development is going, and is based on the
following 3 principles:

  1. Facebook wants to make it easy to do FB integration with a client-side
    mobile app
  2. Facebook wants to accommodate server-side API access in mobile apps
  3. Facebook wants to disallow unbounded refresh of access just because a
    mobile user has granted it once
  4. The developer should not keep his app secret client-side

How they're doing this is to have a client-side flow with short-lived
access tokens which you can get just by using your app ID. I have been
told that on the Facebook side they're doing client authentication before
granting a short-lived access token, the request has to come from your iOS
or Android app credentials that you have registered with your Facebook
app. This means that a server cannot request via the short-lived access
token flow.

To get a long-lived access token via the server side flow requires either
a server-side Facebook OAuth2 dance (appropriate for browser based
software, not mobile apps) or the extension of a short-lived token to a
long-lived one. For mobile apps which wish both client and server side
Facebook access, then, the flow is for the mobile client to do the dance
and get the short-lived token, then pass it to the server for the server
to extend and use. If the mobile app client gets uninstalled or is not
running then after 60 days the server is no longer able to use the
Facebook API.

The bad news is that this is all theory so far :) I've been looking at
this issue for just a few days and am starting experimentation with real
code. I did the spring-social pull request to try to seed the effort to
add this new API, because from my perspective getting something added to
spring-social is a long-lead-time item :) My statement about the OAuth2
standard addition is pure speculation.

Thanks for the prompt response. As I develop the TangoMe Facebook
integration I'll keep you posted of anything I discover.

John

On 5/11/12 8:01 AM, "Craig Walls"
<reply+i-4526254-d9935a38b9392b54175b3e1c8a03c3893ba0971a-1728217@reply.git
hub.com> wrote:

See my comments on the other pull request you submitted.

Also, I already have a similar piece of code in place in a local branch
that does not require any changes to Spring Social Core. But as I said in
the other pull request, the endpoint can only be used for client-side
extension of token expiration.

As for this someday becoming part of the OAuth standard, I have serious
doubts. The OAuth 2 spec isn't final yet, but it also hasn't changed
dramatically since around draft 14 and I believe it is inching closer to
being final. Adding something like this would no doubt disrupt that
specification process, further prolonging finalization of the spec.

Furthermore, the OAuth 2 specification already defines the notion of
refresh tokens which solve this problem in a cleaner way than what
Facebook has come up with. It's unfortunate that Facebook is not
implementing the refresh token portion of the specification that Facebook
itself is helping to write. I suspect that the odds are greater that
Facebook will implement refresh tokens before the OAuth 2 spec will
define Facebook's approach. But time will tell...until then, I'm not
eager to add anything to Spring Social Core to directly support anything
provider specific such as this.


Reply to this email directly or view it on GitHub:
#57 (comment)

@habuma
Copy link
Contributor

habuma commented May 11, 2012

Regarding point #3, I see nothing wrong with "unbounded refresh", when it is, in fact, not unbounded. The user may at any time revoke access and shutdown the refresh.

My problem with doing a refresh at the browser-level (via the authorization flow) as FB requires it is that you may not realize that you need to refresh until you're deeper in your application code. A per-the-spec refresh token would allow the server-side code to put its work on hold, refresh the token, then carry on. But browser-refresh will require a lot of special handling to keep the application flow uninterrupted. My other problem with doing a refresh at the browser level is simply that it is not how it's defined in the specification and therefore I must have special-case code to cover FB's non-compliance with the spec.

Your thought on letting a client get the short-lived token and then allowing a server to refresh from that is a valid one and I had thought of that scenario. I'll consider it some more and decide how I want to proceed. It kind of demands that there be an example of that to showcase it, though...as well as some very clear documentation indicating that you shouldn't expect a long-lived token to be extended any further.

I wonder, however...FB's docs say two conflicting things: (1) You can only extend short-lived tokens, not long-lived tokens and (2) you can extend the life of a token every time the user visits your up, up to once per day. So, if you can only extend short-lived tokens, how can you continue to extend the life of a token once per day if the only token that will survive into tomorrow is the long-lived token?

And, what happens if you get a short-lived token, extend it to a long-lived token, and then the 60-days is almost up? The short-lived token is no longer any good and you can't refresh long-lived tokens. Therefore you're going to have to go through the authorization flow again anyway.

And, it's unclear on what FB means when they say "client-side". I interpret it as JavaScript code, but others interpret it as mobile (iOS/Android) code. It may even be that both are true. But if there's any truth to the JavaScript part of it, then how would a client extend the life of a short-lived token unless it either relies on the server to do it or if it carries the secret in the JS code.

@habuma
Copy link
Contributor

habuma commented May 11, 2012

This wasn't what I had planned to work on today, but...since it is a hot topic and will become more important as we approach July 5, I thought I'd give it another look.

I've done another sweep of tests and confirmed that the initial token granted to the server side is a long-lived 60-day token. I've also confirmed that calling the aforementioned endpoint with that token does nothing to extend the life of it. (Of course, I really should try again tomorrow to be certain that I'm not just hitting the once-per-day rule...but I have confirmed this in the past.)

As I mentioned before, no matter how you end up with a long-lived token (client or server), you're going to need to freshen it up before the 60 days is up and the only way to do that is to go through the authorization flow again (because you can only extend the life of short-lived tokens, not long-lived tokens). Fortunately, this doesn't bother the user because as long as the user has not revoked access, the authorization will still be good and Facebook will immediately redirect back to the app with a new authorization code to exchange for a fresh new access token.

Therefore, whether the token has expired or not, the only way to get a fresh long-lived token after the initial long-lived token (as I understand it) is to go through the authorization flow again.

So, one approach is for you to purposefully take the user through the authorization flow when they first come to your app on a given day and replace the existing connection with the new connection data. Or, you could wait until an ExpiredAuthorizationException is thrown and go through the process then. (Actually, it might be good to do this for any NotAuthorizedException.)

At the moment, there's no support for either case in Spring Social. But here's how it might work (none of this is confirmed...just some sketchings I made):

  • For the once-per-day case, you could have a servlet filter or Spring MVC interceptor that checks to see when the last refresh was performed and if it hasn't been done yet for that day, store away the original request, take the user through the authorization flow (replacing the existing connection, if any), then bring them back and re-issue the original request.
  • For the NotAuthorizedException/ExpiredAuthorizationException case (which, btw, would be useful for more than just Facebook), you could have the same filter or interceptor catch the exception, stores away the current request, goes through the authorization flow (replacing the existing connection, if any), then reissues the original request.

These descriptions are purposefully brief so as not to get lost in the details of what should happen. I'm certain that gotchas will be encountered that I haven't thought of.

One potential gotcha is dealing with the transactionality of the original request. Spring's transaction support should rollback anything that can be rolled back when a runtime exception is thrown, but what about non-transactional work that may have been done prior to the exception being thrown? Say, if a tweet was posted to Twitter and then an attempt to post to a person's Facebook wall is met with a NotAuthorizedException...how would that be handled? (Truth is, that's a problem right now and not only with Spring Social, but with any app that could perform non-transactional work before a transaction-killing exception is thrown.)

@habuma
Copy link
Contributor

habuma commented May 11, 2012

The aforementioned filter/interceptor could also check for how much life is left on a token and if it falls under some configurable threshold go ahead and do the authorization flow as described above. (As an option to the once-per-visit-per-day approach.)

@JohnWPhillips
Copy link
Author

I believe the problem that Facebook may be trying to solve is that when a
mobile user uninstalls an app it does not remove that vendor's server-side
access grant to that user's data. The Facebook user can remove this
access grant manually via the Facebook web page, but this is not obvious.
However, with this new change any vendor whose app is uninstalled will
eventually lose the ability to access the user's data.

Because of this new change a mobile application vendor who wishes to have
client side and server side access to the Facebook API has the following
options:

  1. Do OAuth2 dance client side and pass the token to the server
  2. Do OAuth2 dance server side and pass the token to the client
  3. Do OAuth2 dance both ways
  4. Proxy Facebook API requests from client/server to server/client

Of these, getting the token client side, passing it to the server, and
extending it server-side seems like the easiest and most secure way to go.
Facilitating this flow may be why this new endpoint has been added.

In the following link
http://developers.facebook.com/roadmap/offline-access-removal/ Facebook
says of its new endpoint "the endpoint can only be used to extend the
short-lived user access_tokens. If you pass an access_token that had a
long-lived expiration time, the endpoint will simply pass that same
access_token back to you without altering or extending the expiration
time." You can try getting a short-lived access token and extending it
with the following test:

  1. Run something on http://localhost:8080
  2. Set your Facebook "Site URL" to http://localhost:8080 (assuming your
    app is still sandboxed/under development)
  3. Request Short-lived access token in your browser
    https://www.facebook.com/dialog/oauth?client_id=&redirect_uri=http://localhost:8080&scope=user_about_me&response_type=to
    ken

The access token has a ~1-2 hour access period

  1. Extend to a long-lived access token
    https://graph.facebook.com/oauth/access_token&client_id=&client_secret=&grant_type=fb_exchange_token&fb_exchange_token=<short lived token
    from step 3>

The token returned has a ~60 day expiration period

On 5/11/12 1:24 PM, "Craig Walls"
<reply+i-4526254-d9935a38b9392b54175b3e1c8a03c3893ba0971a-1728217@reply.git
hub.com> wrote:

The aforementioned filter/interceptor could also check for how much life
is left on a token and if it falls under some configurable threshold go
ahead and do the authorization flow as described above. (As an option to
the once-per-visit-per-day approach.)


Reply to this email directly or view it on GitHub:
#57 (comment)

@habuma
Copy link
Contributor

habuma commented May 14, 2012

Your explanation of how this impacts mobile makes sense, but I don't see how the same thing couldn't have been accomplished with a per-the-spec refresh token by keeping that refresh token server-side. Eventually the access token kept on the client will expire and the client will have to rely on the server-side to refresh the token. Conceptually, the endpoint Facebook is providing is no different than a refresh token endpoint would be except that you're using the not-yet-expired access token as the refresh token. Why not just implement the spec's refresh token and give guidance to mobile developers to handle the refresh on the server?

And yes, I've performed those same steps you described probably hundreds of times while investigating this. My question remains: What happens when that 60-day token you obtained in step 4 starts growing stale? You no longer have a valid short-lived token to extend it any further and you can't extend a long-lived token. So you have no choice but to do the authorization again.

@JohnWPhillips
Copy link
Author

I'm not arguing for/against the Facebook changes, I'm just trying to
understand and design to them. I'd like to ask you to consider adding
this new API call to spring-social-facebook, as it's likely that I'll be
using it in my Facebook integration implementation. Also, I imagined that
you personally had walked through the new API using steps similar to the
ones previously outlined, I more thought it was worth adding them to give
context for anyone else reading this thread.

On 5/14/12 8:02 AM, "Craig Walls"
<reply+i-4526254-d9935a38b9392b54175b3e1c8a03c3893ba0971a-1728217@reply.git
hub.com> wrote:

Your explanation of how this impacts mobile makes sense, but I don't see
how the same thing couldn't have been accomplished with a per-the-spec
refresh token by keeping that refresh token server-side. Eventually the
access token kept on the client will expire and the client will have to
rely on the server-side to refresh the token. Conceptually, the endpoint
Facebook is providing is no different than a refresh token endpoint would
be except that you're using the not-yet-expired access token as the
refresh token. Why not just implement the spec's refresh token and give
guidance to mobile developers to handle the refresh on the server?

And yes, I've performed those same steps you described probably
hundreds of times while investigating this. My question remains: What
happens when that 60-day token you obtained in step 4 starts growing
stale? You no longer have a valid short-lived token to extend it any
further and you can't extend a long-lived token. So you have no choice
but to do the authorization again.


Reply to this email directly or view it on GitHub:
#57 (comment)

@habuma
Copy link
Contributor

habuma commented May 14, 2012

Completely understood. I did not mean to give the impression that I disagreed with your thoughts. I, too, am trying to get my head around this thing Facebook has done.

That said, I will certainly consider adding the API call. But I do not plan to add it in such a way that it requires any changes to Spring Social Core just to support a Facebook-only quirk. I will seek out a solution that keeps the changes confined to Spring Social Facebook.

@Polve
Copy link

Polve commented Aug 6, 2013

I've the same need to extend a short lived access token, but I see this thread is old.

Is there any "standard" method in spring-social-facebook to do it?

@Voxelot
Copy link

Voxelot commented Oct 23, 2013

I'm also looking for this feature, what is the latest news on this?

@wongwill86
Copy link

Also looking for this or similar feature. Not sure if there is a better newer way to do this but it seems like I'm leaning towards @JohnWPhillips's

"Of these, getting the token client side, passing it to the server, and
extending it server-side seems like the easiest and most secure way to go.
 Facilitating this flow may be why this new endpoint has been added."

Also I'm not sure if there was a change to the FB api, but they don't advertise the OAuth2 method of grant_type='refresh_token' anymore. Calling the Facebook/Oauth2Template's refreshAccess function doesn't seem to work. Crafting the post manually doesn't seem to work either.

To use the facebook extend api, I commandeered the existing refreshAccess function by:

MultiValueMap<String, String> params = new LinkedMultiValueMap<String, String>();
                params.add("fb_exchange_token", authToken);
                params.add("grant_type", "fb_exchange_token");
                AccessGrant accessGrant = getConnectionFactory().getOAuthOperations().refreshAccess(authToken, 
                        params);

Is there a better way to do this?

@pivotal-issuemaster
Copy link

@JohnWPhillips Please sign the Contributor License Agreement!

Click here to manually synchronize the status of this Pull Request.

See the FAQ for frequently asked questions.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Development

Successfully merging this pull request may close these issues.

6 participants