I'm developing a Facebook app that will run in canvas. This is the scenario (I think it's very common). This is all server-side implementation.
Being:
APP_URL: https://apps.facebook.com/xxxxxx/
CANVAS_URL: https://myexample.com/facebookApp/
Step 1 (index of the app) has a form. It has action="CANVAS_URL/step2" (note that is not the app Url). In order to use signed_request in the next step, it has an hidden field <input type="hidden" name="signed_request" value="<?php echo $_POST['signed_request'] ?>" />
Step 2: it receives the info of the form and stores it in a Session, then parses the signed_request. This works OK. I store it in the Session because I want to save it to a database after the user is authenticated. If user was logged on to the app, I redirect him to APP_URL/step3; if not, I redirect him to login dialog, with &redirect_uri=APP_URL/step3 . In both cases, note that the step 3 is APP_URL/step3 (as I need signed_request again to check if user has authenticated and another data). All redirections are made with JavaScript: <script type="text/javascript">window.top.location = "URL";</script>
Step 3: now I want to save the data previously stored in the Session. BUT as the user is navigating through FB canvas, the session data is not available.
I tried several combinations. If the form is sent to APP_URL/step2 (instead of CANVAS_URL/step2, in order to create the session for APP_URL), I can't retrieve the posted data (because it is sent to FB, not to the CANVAS_URL).
I thought about using the session_id to recreate the Session in APP_URL, but I'm afraid that it isn't a very secure approach. I'm sure that there must be a better workaround.
Related
This question already has answers here:
Prevent user from seeing previously visited secured page after logout
(7 answers)
Closed 6 years ago.
I have a JSP page with a form where you fill up certain details. On submitting the form i call a servlet which then updates details in a database after that i redirect to a page i display a confirmation message to the user.
The problem i have here is when the user clicks back he goes to the form page again where if he clicks a submit button, it creates a new record in the database.
Consider this similar to a shopping cart example where in this case he would buy the same item twice. But the only problem here is i cannot handle this in backend, i.e. the database can never know a redundant operation is taking place.
I need to handle this from the client side.Bit weird but i have to do it this way.
Basically when the user clicks the back button i don't want him to be able to go to the form page and may be just to some other page.
This is a typical HTML form issue, you can solve it through any one of following methods
1) Server side (page expiration): Since you've mentioned that the page refreshes and goes to the confirmation. You can set no-cache and add a page expiration time as -1 to the page you have that form.
So that when user presses the back button, he will see that the page has expired. He will be forced to load the page freshly. This is the behavior that I see in most banking websites.
Response.Buffer = True
Response.ExpiresAbsolute = Now() - 1
Response.Expires = 0
Response.CacheControl = "no-cache"
2) Using turn Key method: When the form loads, you can generate a random key in session memory and also embed it as a hidden value in the page.
During form submission, check the submitted hidden key against the value in session. If exists, then add the entry to database otherwise you can reload the page with a fresh key for the user (who might have done that unintentionally).
In load balanced or web farms, consider persisting the turn key in Database against the current user.
3) Client Side (Not recommended) : You can restrict the user from using the browser back button by removing the page from the history. (One side effect is that it will remove the entire history for that browser tab/window)
history.pushState(null, null, document.title);
window.addEventListener('popstate', function () {
history.pushState(null, null, document.title);
});
If you need the same support for older browsers where push and pop states are not supported, you can use following snippet.
<script>
function preventBack() {
window.history.forward();
}
setTimeout("preventBack()", 0);
window.onunload = function() {
null
};
</script>
Before redirecting to the JSP page send these headers with the response from the controller so that the page is not stored in cache and the browser will always request a new page from the server.
response.setHeader("Cache-Control", "no-cache, no-store, must-revalidate"); // HTTP 1.1.
response.setHeader("Pragma", "no-cache"); // HTTP 1.0.
response.setDateHeader("Expires", 0);
So every time you go back to that page a new page will be requested from the server and will be displayed with the cleared input fields.
You could implement a PRG-Pattern (https://en.wikipedia.org/wiki/Post/Redirect/Get) using php.
Basically, after successfully submitting the form you redirect to a confirmation page which informs the user that their submission was successful/accepted/etc. and also set a variable which you can later use to check if said submission has been submitted yet.
When the user tries to reload said page or go back you can check the variable and display either the form again or the confirmation page based on if the submission has been submitted already.
I think following flow is the best:
Client submits data to server
Servlet processes it
It returns HTTP 303 redirect to client
Client redirects to success page
After this flow, refresh, back button will work properly.
For more information read Simple Post-Redirect-Get code example
I am reading over Creating an efficient REST API and I understand and agree with a lot of what the document has to say.
I'm currently implementing a Twitter clone and each tweet object in the database has a list of readers (user IDs).
According to the document, it seems like the request for this would be something like:
PATCH /tweet
{read: true}
However read is not an actual property of the tweet. Instead this would actually update the list of readers on the tweet with the currently authenticated user. Is this correct?
This also means that the user could potentially perform other operations on the tweet using this same API route, but users cannot actually update/PATCH tweets in any way other than to mark them as read or not.
Additionally, only an authenticated user should be able to do this and it should only update the list of readers for them. The document says that you should not handle state in your API which means no sessions -- however authentication is obviously necessary. It seems to say that you should send the authentication/authorization token each time, but I don't understand how this is substantially different from a session cookie in some cases. Is the implication that the request should actually be:
PATCH /tweet
<Authentication-Header>
{userId: userId, read: true}
i.e. should the API itself not try to use information from the session and force requests to provide logged-in user information? Is there a better way to structure this?
Depends on what level of authentication is necessary.
Keep it simple. No need to complicate things.
Do you really need a "state"? You have a database where you have to check the user authentication. Why not use the database record?
If the API will be used by the client on their web server, then you just have to check the IP address of the request.
The IP address cannot be spoofed and therefore is a very good authentication factor. Add username and you have multi-factor authentication
Instruction for client using PHP
$user = 'joe';
$param = 1;
file_get_contents("http://apisite.com/api/function/?id=$user¶m=$param");
An HTML form, maybe you'd have to restrict access but if they name name the page something not easy to guess, then you have it.:
<form action="http://apisite.com/api/function/">
<input type="textarea" name="tweet"/>
<input type="hidden" name="user" value="joe" />
<input type="hidden" name="param" value="1" />
</form>
If the API were to be done in PHP:
PHP
<?php
header('Content-Type: text/plain; charset=utf-8');
header('Cache-Control: max-age=0');
$ip = $_SERVER['REMOTE_ADDR'];
$results = mysqli_query("SELECT COUNT(*),`user`,`state` FROM `Profile` WHERE `ip` LIKE '$ip' ");
$row = mysql_fetch_array($results, MYSQL_NUM);
if ($row[0] == 1 && $row[1]==$_GET['user']) && $row[2]==1){
$tweet = trim($_GET['tweet']);
$param = $_GET['param'];
include('tweet.php');
echo "Sent\n";
}
else{
echo "Not Sent\n";
}
?>
The problem with using cookies is that you are moving state outside of the URI and into the cookie.
RESTful APIs should have all the state necessary in the URI. Believe it or not but this does make things a lot easier as you don't have to mess around with handling cookies if you are creating a new client library.
Not using cookies also makes for really nice integration with other devices. Say I am viewing a RESTful webpage on my browser, and then I want to show someone something on my account. I can email them the URI /some-uri?authToken=1234 and they will be able to view my account without me giving them my password!!
Once the token expires, they will no longer be able to access the account.
EDIT
If you put the token in the POST body, a user won't be able to visit a page by typing in the address bar. They will keep being told they are not authorised. So yes, you need it in the URI. You can use a cookie if you really want, but except that it is a workaround.
You'd get the login token by exposing a login resource. To login you would send your username and password to:
POST /login
This would create a /login/{token} resource. You can use this token (or even the full URI if you want) to authenticate with the server.
When you want to log out, you call
`DELETE /login/{token}`
If you try to use the token now, the API should return with 'not authenticated' as the token no longer exists.
I am authotizing my app in the following way:
// authorize app!
$('#authApp').click(function(){
var oauth_url = 'https://www.facebook.com/dialog/oauth/';
oauth_url += '?client_id=#{app.id}';
oauth_url += '&redirect_uri=' + encodeURIComponent('https://www.facebook.com/pages/null/#{fbPageId()}/?sk=app_#{app.id}');
oauth_url += '&scope=user_likes,user_photos';
oauth_url += '&app_data=7B%27game%27%3A+%27key%27%7D';
oauth_url += '&state=sbSbsbSb';
As you can see I am setting the 'state' param as part of the query string.
Now when the user authorizes the app he is redirected to the redirect url.
However the data passed in the state parameter is not posted to my app nor is it part of my app's iframe query string. I was expecting to find it as part of the signed request, but no. This is the deserialized signed request posted back after authorisation
{ algorithm: 'HMAC-SHA256',
expires: 1348927200,
issued_at: 1348921162,
oauth_token: 'AAA...',
page: { id: '490...', liked: true, admin: false },
user: { country: 'ec', locale: 'en_US', age: { min: 21 } },
user_id: '1...' }
I do see that the state is included of the parent page's query string. I need to access that parameter from my app (running inside an iframe). I believe that I cannot just access the parent page's window location because of same origin policy restrictions.
I have read through the documentation and searched online. Persisting data across an app authotization needs to be done using the state parameter. However it is nowhere stated how to retrieve that state param once redirected back to your app.
This is from the facebook doc's regarding the state param:
A unique string used to maintain application state between the request
and callback. When Facebook redirects the user back to your
redirect_uri, this parameter's value will be included in the response.
You should use this to protect against Cross-Site Request Forgery.
Am I supposed to get the state data back from the parent's page query string?
Or am I doing something wrong?
* EDIT *
I am storing the user to user request Id in the state parameter. For example A invites B to participate in the app via a facebook request. Once B authorises the app A needs to be rewarded. So I need to know that B came to the app following A's invitation. Therefore I store the requestId in the state param, so once B has authorised the app I can take appropriate action.
* EDIT 2 (SOLUTION) **
If your redirect_uri is pointing to the Page Tab URL then facebook will NOT send back the state parameter! It will only be sent back if you redirect to the Canvas URL !!!!!
The Facebook documentation you referenced is a bit confusing. The only thing you should be doing with the state parameter is making sure you are not a victim of CSRF. Facebook's server side authentication flow gives an example of this in PHP. In short, you should be storing the state value in the session and then verifying that the session value is the same as what Facebook passes back to you in the request. The key line in their PHP example is:
if($_SESSION['state'] && ($_SESSION['state'] === $_REQUEST['state'])) {
// Continue with application logic here because state matches.
// Otherwise, exit immediately because you're a victim of CSRF!
So back to your problem. From your redirect URL and the response you are getting, it's obvious your app is on a Facebook Page Tab. See the authentication flow for page tabs for how you should be doing this. Note they are not using the state parameter in step 2 and that the state parameter is never mentioned in page tab authentication flow. So even if you wanted to use the state parameter for something other than its intended use, you are out of luck.
Based on your edits, I suggest you check out the documentation on requests. Note that the user clicking on the request will be redirected to your canvas app, not the page tab. "The canvas URL will also contain an additional GET parameter request_ids, which is a comma delimited list delimited list of Request IDs that a user is trying to act upon." So there is no need for you to be trying to do this yourself.
Am I supposed to get the state data back from the parent's page query string?
No, not when authenticating within a canvas/page tab app. The only query string parameter that gets passed to your app in this scenario is the content of the app_data parameter.
But you don’t need the state parameter in this scenario – verifying the signed_request is absolutely sufficient, because it’s signed with your app secret, that only you and Facebook know. So that is enough protection against “manipulated” requests right there already.
See https://developers.facebook.com/docs/authentication/canvas/ resp. https://developers.facebook.com/docs/authentication/pagetab/ for more details. (And see how they do not mention the state parameter at all.)
Edit:
I am storing the user to user request Id in the state parameter. For example A invites B to participate in the app via a facebook request. Once B authorises the app A needs to be rewarded. So I need to know that B came to the app following A's invitation. Therefore I store the requestId in the state param, so once B has authorised the app I can take appropriate action.
That’s a misuse of the state parameter … it’s supposed to achieve something completely different (CSRF protection, as the docs say).
While this might work in your scenario – why are you not using the app_data parameter to transmit this piece of information? That’s the designated way of transferring info to canvas/page tab apps.
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()
I have a secured event type, which will check to see if the user is logged in, and the redirect them to the login page if they're not:
<event-types>
<event-type name="securedPage">
<before>
<broadcasts>
<message name="CheckLogin"/>
</broadcasts>
</before>
<results>
<result name="NeedLogin" do="page.login" redirect="true"/>
</results>
</event-type>
</event-types>
The login page has a form that links to an action.login event handler, which will redirect to the user's homepage if login was successful, and redisplay the login page if it wasn't:
<event-handler name="action.login">
<broadcasts>
<message name="Login"/>
</broadcasts>
<results>
<result name="LoggedIn" do="page.user.home" redirect="true"/>
<result name="NotLoggedIn" do="page.login"/>
</results>
</event-handler>
This code works great, but the problem I'm running into now is that it will always redirect to the user's homepage, even if they requested another page. Since the form posts to the action.login event, and the result do is hard-coded to page.user.home, how can I modify my code to keep track of which page the user originally requested and redirect them back there?
In the logic you use to determine if a user needs to be logged in, you can grab the current event and store it in a shared scope (like the session scope).
Then, after a successful login simply call event.forward() and pass in that value.
here's the general idea:
in whatever method/code runs to 'secure' your page, if a user is not logged in, grab the url they were trying to reach and set it back to the viewstate as a variable before you forward them. the CGI scope will be your friend here, particularly CGI.query_string.
on your sign/log in form, check for this 'returnEvent' variable in the viewstate and set it as a hidden value on your form if it exists.
someplace in your sign up/in process after success, check for this 'returnEvent' variable and if it exists, forward the user on.