The case:
We plug-in FB JS and init it with FB.init(). This call creates fbsr_NNNNN cookie. The cookie has session-limited expiration date (until browser is closed). We call FB.init() only once in this example. After that we call the pages that don't contain FB.init() invocations so it doesn't have a chance to renew the access_token
We perform authentication and make some server-side (PHP FB SDK) call, like /me
Wait for 30 minutes or something until FB session expires
Perform the /me request again and see "An active access token must be used to query information about the current user."
This happens because current php sdk implementation:
public function getSignedRequest() {
if (!$this->signedRequest) {
if (isset($_REQUEST['signed_request'])) {
$this->signedRequest = $this->parseSignedRequest(
$_REQUEST['signed_request']);
} else if (isset($_COOKIE[$this->getSignedRequestCookieName()])) {
$this->signedRequest = $this->parseSignedRequest(
$_COOKIE[$this->getSignedRequestCookieName()]);
}
}
return $this->signedRequest;
}
just takes the access_token from cookies as-is and in case of exception it doesn't clear it. So the code has no chance to return into normal workflow without manual cookie removing. Yes, if I delete the cookie - the code starts to work again (as long as there is no saved access_token and library fetches the new actual one).
So what workaround for this issue would you propose? What do you use? Do you think it is a bug?
UPD: seems like there is a possible workaround: to extend Facebook class and override the method that cleans persistent storages. For details look at discussion to the answer http://facebook.stackoverflow.com/a/8294559/251311
But I'm personally still sure that FB SDK should handle it without any additional hacks
First: I have no experience with Facebook itself, but the OAuth 2 RFC specifies a refresh_token - consider implementing it.
Second: Facebook returns an error, right? If that error occurs just unset the cookie. If that doesn't work with your current implementation you're doing something wrong - pretty much every Twitter library I have seen (also uses OAuth, albeit 1.0a) uses its own HTTP wrapper. Rather than giving back an URL to request you simply execute the request yourself.
Third: What if you simply set a timeout on the cookie? I'm rather sure OAuth also gives you an expires_in value, simply use it (do take 5 seconds off this value, because of network lag etc).
Related
I am creating a website using Parse, and I have run into an issue. When I let my browser sit around for a couple hours, calls to the graph API don't seem to work anymore. The response from the graph api is:
Error validating access token: Session has expired on Wednesday,
29-Apr-15 00:00:00 PDT. The current time is Wednesday, 29-Apr-15
21:34:32 PDT.
Of course, my token has expired... but now I can't find a way to easily refresh my access token without sending the user back through the login process... which isn't an ideal workflow.
A glimmer of hope in the Facebook Javascript documentation has me wondering if I am potentially just doing something wrong. If not, their documentation is horribly misleading.
Also keep in mind that the access tokens that are generated in
browsers generally have a lifetime of only a couple of hours and are
automatically refreshed by the JavaScript SDK. If you are making calls
from a server, you will need to generate a long lived token, which is
covered at length in our access token documentation.
source: https://developers.facebook.com/docs/facebook-login/login-flow-for-web/v2.3#token
What can we do to automatically refresh this token?
The JS SDK should automatically provide fresh access tokens, yes – although I am not sure if it does so if you just have the app open for a couple of hours without interacting with it (meaning: reloading the page, so that the JS SDK gets re-initialized as well). Since this is not a very common use case, it might not.
I would suggest that you try and use FB.getLoginStatus to get a fresh access token. (There should be no need to then set this access token explicitly on subsequent API calls; that the SDK will handle itself.)
This method uses an internally cached result, to avoid having to make a request to Facebook every time it is called. Since the expiry time of a token is known, I would expect the SDK to be able to decide itself if an actual request is necessary again – but if it doesn’t, you can also force a new request by setting the second parameter to true. (This is however not recommendable for every call, so I would try and see if calling the method without it is able to resolve the problem already.)
A simple workaround is to make an API call before the current access_token expired.
Let's say the token expires every 2 hours, you can, for example, make a simple /me call every 1H30 :
/**
* Make an API Call to refresh the access_token
*/
function tokenRefresh() {
FB.api("/me", function (response) {
// the token is refreshed
});
}
// call it every 1H30
var tokenTimeout = window.setTimeout(tokenRefresh, 5400000);
If you don't want that code be executed when your user uses the app, so when it will be useless, you can, each time you make a request, reset the timeout in the callback :
window.clearTimeout(tokenTimeout);
var tokenTimeout = window.setTimeout(tokenRefresh, 5400000);
Therefore, your app makes a call each 1H30 that passed without an API call.
We're writing an app with an Android view and a website. Our app needs to associate some data with Facebook, Google, or Twitter (call them 'FGT'), and then hit the associated service to return true if that data is authenticated. The control flow is...
Android generates some data
User selects an FG or T account
getAuthToken
upload the data+token, via HTTPS POST, to our website
Our website (in Django, not that it matters) sends the Auth Token to FGT
FGT returns true if it likes that token, and false if it doesn't
The goal is preventing an attacker from concocting data and throwing it at our site with curl. And we /don't/ need to upload the data all the way to F, G, or T. We don't need to go all the way to a Hybrid App, where our webservice authenticates itself to F, G, or T, and then uses F, G, or T's API to post, e-mail, or tweet in the user's name.
There's obviously a simple answer for this out there somewhere; hopefully just three URIs, one each for F, G, or T, into which I can insert the Auth Token. That's why I would prefer NOT to download and install the SDK for each of Facebook, Google, and Twitter, and then write tons of service-specific code for each case. That would clutter up the code, and leave me screwed when the client requests Tumblr or MSN.
(A related question: What's the second parameter of getAuthToken()? "ah"? "mail"?)
So, while I continue to read books, source code, and SO posts showing how to do hard things the hard way, can someone tip me off how to do an easy thing the easy way?
The thread "validating Android's authToken on third party server" answered the sub-question "what simple URI to hit to test a token?"
The magic uri, for Google, is: https://accounts.google.com/o/oauth2/tokeninfo?access_token=%token_here%
The next question is How to thump an Access Token out of getAuthToken. The scope there should be the minimum possible to show a user really lives here:
String SCOPE = "oauth2:https://www.googleapis.com/auth/userinfo.profile";
am.getAuthToken(a, SCOPE, false, this, null);
My this class implements AccountManagerCallback<Bundle> and provides the method run(AccountManagerFuture<Bundle> result). That, per documentation such as "Android AccountManagerFuture getResult gives IOEXcelption when trying to get authorization token", might call startActivity() to challenge the user to authorize this activity, or it might use a token that's already stored in the AccountManager. (And note that implementing a class just to call-back yourself is pernicious, especially if the target method's named merely run(), so do as I say not as I do, kids!)
The resulting token is 52 bytes long, beginning with 'ya29.', so it's _ probably _ an access_token, not 331 characters, which is probably an id_token.
The resulting token is not bound to any specific channel (where "channel" is one unique set of client, server, and scope). So, from a simple curl, I can hit that /tokeninfo URI with it, and get this (scrubbed) JSONic wisdom:
{
"issued_to" : "442575845966-mde4be7eingpb5pntfs839jipsetro6s.apps.googleusercontent.com",
"audience" : "424242424242-mde4ab7defghi5jklmn839opqrstuv6s.apps.googleusercontent.com",
"user_id" : "424242424242424242424",
"scope" : "https://www.googleapis.com/auth/userinfo.profile",
"expires_in" : 2272
}
And so this answer would have formed, for me, the missing link between all the other documentation I was trying to read. Aaand now I need to do it all again for Facebook & Twitter...
There is a simple URL.
Each authtoken is granted against a scope. Each scope allows the authtoken to do certain things. If you try to do something that the scopes permit, that thing will fail or succeed based on the validity of the authtoken.
The simplest scope to request is probably 'email'.
If you go to the Oauth2 Playground at https://developers.google.com/oauthplayground/ you can experiment with scopes and calls to get one that suits you. You'll be able to see the URLs that you then need to replicate in your app.
So, I've been at this for a while now, went through a bunch of different questions, and still no solution.
If I log in regularly, all is fine, I can logout as expected. But, if I login with facebook (authorize the app), then there is no way to logout unless I manually delete the cookies from within my browsers menu.
Following that logic, I wanted to destroy the cookies in the logout action using this code(after I modified my session settings to work across subdomains, for my particular case):
function logout() {
if ($this->Cookie->read('Auth.User')) {
$this->Cookie->delete('Auth.User');
}
$this->Auth->logout();
unset($_SESSION['fb_MYAPP_ID_user_id']);
unset($_COOKIE['fbm_MYAPP_ID']);
unset($_COOKIE['fbsr_MYAPP_ID']);
unset($_COOKIE['CAKEPHP']);
//pr($_SESSION);pr($_COOKIE);exit(); //here I see that the cookies are in fact deleted
$this->redirect($this->Auth->logout());
$this->redirect('/login');
}
But every time after the logout redirect it brings the user back, logged in, and the session/cookies recreated.
I went through a lot of SO questions and answers and none worked for me. Any ideas?
You cannot simply unset cookies from the cookie container, this is just the server side representation of the cookies contained in the request.
To delete cookies you need to set the exact same cookie (domain, path, name) but with an expiration that has passed - when read by the client this will cause the cookie to not be sent with the next request.
You can see how this is done in https://github.com/facebook/facebook-php-sdk/blob/master/src/base_facebook.php#L132.
I ended up using a combination of the following answers:
CakePHP + Facebook
$facebook->getSession() call breaks page below the call
The code on the first one is more complete, but is outdated. I also kept the unset() calls that I have in my question, and it seems to work good for now.
I am using the PHP SDK getLoginUrl() function which works perfectly to log the user in. Once the user is redirected back to my page, the URL can come in two forms, see in the following link subsection 3: http://developers.facebook.com/docs/authentication/server-side/
Part of the return URL is a ?state= value. This value is supposed to be used to prevent Cross Site Request Forgery: http://developers.facebook.com/docs/reference/dialogs/oauth/
Though, using the getLoginUrl() method I can never set a state value as it is not one of the parameters: http://developers.facebook.com/docs/reference/php/facebook-getLoginUrl/
So how can I utilize the state-value to log a user into facebook and prevent CSRF?
So how can I utilize the state-value to log a user into facebook and prevent CSRF?
This is being automatically handled by the Facebook PHP SDK. If you were about to write your own API calls to Facebook, you would need to submit the state manually (if desired) as per Facebook's OAuth documentation.
When you create a login url with BaseFacebook::getLoginUrl(), the first thing the function does is to establish CSRF token state1, which creates a hash using PHP's core mt_rand(), uniqid() and md5() functions and also stores the value as a session variable.
When the user gets redirected back to your page the, FBSDK checks if the submitted state matches the state value in the session. If the values indeed match, the state is cleared from the Facebook object and from the session, so all subsequent getLoginUrl() requests would get a new state variable.2
Theoretically you could use your own state value with FBSDK by writing it to fb_<your_app_id>_state session variable before constructing the Facebook-object, as the BaseFacebook's constructor and establishCSRFTokenState() both check if the state already exists in the session.
But that would probably introduce more complexity than is necessary.
see BaseFacebook::establishCSRFTokenState()
see BaseFacebook::getCode()
Well there seems to be quite a bit of confusion on this topic and I am struggling to get a clear answer, so here is my question...
I am using the serverside flow to obtain access tokens for my web app, I previously used offline_access which is now being depreciated so I need a way to refresh the token in the following situations:
1) User changes FB password
2) Token expires naturally
My app posts results to users FB walls so the refresh needs to be done automatically by our server (no cookies or OAuth dialogs)
I thought I could try to use the new endpoint described here
http://developers.facebook.com/roadmap/offline-access-removal/
, with the following piece of code (Java):
public static String refreshFBAccessToken(String existingAccessToken)
throws Exception{
//Currently not working
String refreshUrl = "https://graph.facebook.com/oauth/access_token?
client_id="+FacebookApp.appId+"
&client_secret="+FacebookApp.appSecret+"
&grant_type=fb_exchange_token
&fb_exchange_token="+existingAccessToken;
URL url = new URL(refreshUrl);
URI uri = new URI(url.getProtocol(), url.getHost(), url.getPath(),
url.getQuery(), null);
String result = readURL(uri.toURL());
String[] resultSplited = result.split("&");
return resultSplited[0].split("=")[1];
}
But this doesnt seem to work (I get a response 400), and when I re-read the documentation it seems this endpoint is used for tokens obtained using the client-side flow only...
So what about the serverside flow....?
Can someone tell me if the approach above is correct or there is another way?
Many thanks
From what I understand there is no server side flow for refreshing tokens.
The refresh token call needs to include the response of the user authentication process which is a short lived token.
You will need to include the refresh token process as part of the user login flow or if this doesn't work for your setup you will need to email the user asking them to come back!
I dont know java but syntax is very much like C#, so I can say,you are doing everything right.
But I doubt what does this function readURL do ?
If it works like get_file_contents() of php (i.e. if it does an HTTP get) , I guess thats not a right way to do .
Based on my experience on google's refresh token method, I think you should do an HTTP POST instead of HTTP GET to given url.