How to transparently renew the Facebook access to

2019-03-11 04:34发布

问题:

I have a WCF service which runs in IIS 7.5 and VS 2010. This service has some methods which internally use the Facebook C# SDK (version 4.1, not latest) in order to perform some GET and POST from/to Facebook. Since Facebook will soon remove the offline_access I have to handle the situation where an access token is expired.

I have understood the way the authentication is performed (in order to get the code and after having the code to get the access token) in order to use the Graph API for getting Facebook information (as presented here).

I have two questions:

  • When my service method is called and I retrieve the token of the appropriate user from my DB, is there a way to know if the access token is expired or not?
    I have read that when a Facebook API call is performed and the access token is expired then the following exception is thrown: OAuthException. But is there any better way to detect the expiration? I don't want to
    1. Call the Facebook API
    2. Handle the exception
    3. Renew the access token and finally
    4. Repeat the initial call with the new access token.
  • Is it possible to renew the access token of the user transparently (store it also in the DB) and continue to handle the service method? In this resource, the important ("Renewing an Access Token") part is missing (declared as [todo])

I would like to achieve the following scheme, in the implementation of the Service Method:

sc = SocialNetworkAccountDao.GetByUser(user)

isExpired = call method to check if the sc.token is expired.

if (isExpired)

{

  newToken = call method for getting new access token

  sc.token = newToken;

  SocialNetworkAccount.Update(sc);

}

Facebook = new Facebook (sc.token)

Facebook.Post( ..... )

--

The process to communicate with the QAuth dialog is asynchronous (a redirect is performed), and the communication with access token url to get the access token this is performed synchronously.

Final Question:

  • Is there a way the service method to wait for the new access token we retrieve from the request / callback with Facebook in order to continue later with the new access token?

回答1:

I don't know about the C# SDK, but ALL facebook SDKs are basically just wrappers for http request to the graph different urls.

If you use the server side authentication flow then you should get a long lived token (about 60 days), and you get the expiration time with it, when it expires you need to re-authenticate the user, there's no way to extend the token.

The client side authentication returns a short lived token (about 2 hours) but you can use the new endpoint facebook provided to replace the "offline_token". You can only use that with still valid access tokens.

Also, no matter what, you always get the "expires" time when you get an access token, unless it's an application token which does not have an expiration date.

In either case, if you get a long lived token you can store it in the database and use it on the server side, but be aware that the token can still get invalidated for a number of reasons.

You can also use the official documentation for Handling Invalid and Expired Access Tokens. If the C# SDK doesn't work well for you then you might want to just implement it yourself, for your own needs, should be pretty easy.



回答2:

You should really update the c#sdk to the latest version - they fix a lot of issues over time..

Renewing a token
This is our code that handles the renew call itself (c#sdk ver 6.0.16, but this should work on earlier versions as well):

    /// <summary>
    /// Renews the token.. (offline deprecation)
    /// </summary>
    /// <param name="existingToken">The token to renew</param>
    /// <returns>A new token (or the same as existing)</returns>
    public static string RenewToken(string existingToken)
    {
        var fb = new FacebookClient();
        dynamic result = fb.Get("oauth/access_token", 
                                new {
                                    client_id         = FACEBOOK_APP_ID,
                                    client_secret     = FACEBOOK_APP_SECRET,
                                    grant_type        = "fb_exchange_token",
                                    fb_exchange_token = existingToken
                                });

        return result.access_token;            
    }

Facebook says that the new token may be the same (but extended expiration) or a completely new one, so you should handle that in your logic if required.

According to Facebook, you should call the renew up to once a day. (https://developers.facebook.com/roadmap/offline-access-removal/)

We keep a time-stamp on when the last 'Facebook update' was done, and if that time is over 24h ago and the user is accessing our application, we go in the background and renew the token. (we also update other data, name, email and other things we need)

Apparently, it is not calling it up to once a day, but calling it once per token.

Facebook will not let you renew a longed-live token. See 'Scenario 4:' at the offline access removal page

To clarify: short-lived tokens come from clients, long-lived tokens come from server-side.

In short, when a user access your app, use the SDK of the client to get a new short-lived token send it to the server, and extend it with your server to make it a long-lived and store that, so you get 60 days from that point.

Handling the Expired time
In our current usage, we were not required to keep the track of expire time, as all access starts with a client that checks that, but in the same the code access result.access_token it can access result.expires which returns the number of seconds remaining on that newly acquired token (should be close to 5 mil => 60 days)).

In any case, I am not sure there is a way to get the expire time from a token without making another call to the auth process. I know that the Facebook Debugger returns this, but that does not really helps..

Renewing an invalid token
This will not work, you will get any of the errors as explained here when trying to renew an expired/invalid token.
Remember to call the renew before it expires and remember that when you call renew on an expired (or any other kind of invalid) you will get an error and need to handle it (we handle it outside the code I pasted).



回答3:

Time till expire

Short answer: you get the seconds until it expires, but the c# api appears not to expose it.

According to this facebook documentation, you get the the number of seconds until the token expires as a paramater when you get the token. The documentation lists the response format as :

access_token=USER_ACESS_TOKEN&expires=NUMBER_OF_SECONDS_UNTIL_TOKEN_EXPIRES 

This is backed up by the the draft RFC for Ouath 2 which says responses should contain the expire time.

Unfortunately, it is directly contradicted by the C# SDK documentation that states here that

There is no way to determine if an access token is expired without making a request to Facebook. For this reason, you must always assume that an access token could be expired when making requests to Facebook.

This is not true. If you look at the c# SDK source, the object the sdk creates for the OAuth response explicitly contains ( unfortunately as a private variable) the following code

    /// <summary>
    /// Date and Time when the access token expires.
    /// </summary>
    private readonly DateTime _expires;

So it has the data, but for some reason does not expose it ( at least there, I didn't look to much further). I'd say either dig around the library more and see if its exposed somewhere, fork and patch it on github(i believe it's a one line change), or use reflection to read the private variable in your own code.

Continuing to handle the service method and renew token

Short answer: Maybe. If you knew the token was expired, you could obviosuly get a new one before the request.

If you don't, than you kinda can, but you need to handle your request failing. In theory, this doesn't mean you need to handle an exception, just look at the http status code (which will be one in the 400s probably 403 forbidden ) however C#'s webrequest method interpets non 200 status c as events that raise an exception. Since the API uses this mechanism to make calls, you only get an exception when they fail.

Is there a way for the service method to wait for the new access token we retrieve from the

Sure, after you handle the exception.

Once this happens, asynchronously, you will get a new auth token. So you can, after catching the exception, wait some amount of time,check if you get a new token for that user, and if so retry . If keep waiting and checking. Make sure you have a maximum number of retries and a maximum amount of time to wait. Pulling the database to check for changes is a little inefficient, so you probably want to pick the time interval you check for changes on carefully and potentially make it exponentially back off.

The other, more efficient (for a single server), but complex way, is to have the system that gets the callback with the auth token raise events and have the retry logic listen for those events.

When your code gets an exception because your token is expired, in the catch block make a function that will redo the request and then add it as an event listener to the auth events. Have the function check that the event was for a fresh auth token for the users who request the event and if so, make the request. Again, remember to have a maximum retry count.

It is not possible to actually use async/await or something like that to wait for your request for a new token to cmplete. The problem, refreshing the API token is not request that you can wait on the response for , its actually triggering something that eventually causes a seperate get request to be made
Note, although the above diagram is for when a user first logs in, the same sequence happens roughly on a failed call, but it starts with the redirect.