I have facebook comment box. I want to store the comment in a database when a user comments something. So I am attaching a callback function with FB.event.subscribe('comment.create', ... From there I get commentID and href but the only way to get the exact comment is with FQL which is deprecated from 2011 and nobody knows when facebook will remove it. Using Graph API I can get all comments but there is no way to find out which comment belongs to a specific user of our app (we don't ask for any permissions so there is no access_token; we trigger popup form when somebody comments so it is very important to match user details with comment (that's why we subscribe to comment.create)). Is there a smart way to do this or should rely on a deprecated feature?
Edit:
I am trying to get the comment like this:
FB.api(
{
method: 'fql.query',
query: "SELECT text, fromid FROM comment WHERE post_fbid = '" + resp.commentID +
"' AND object_id IN (SELECT comments_fbid FROM link_stat WHERE url='" + resp.href + "')"
},
function (data) {
var fb_id
, comment
console.log(data)
if ( data.length == 1 ) {
fb_id = data[0].fromid
comment = data[0].text
}
// ...
}
)
The problem is that when on localhost - it returns array with one element - the comment I want. When I upload to my app - then it returns array with no elements. Maybe there are permission issues. My question is how to get the content of a comment when submitted. What is the canonical way? Is it possible without access_token and permissions?
FQL isn't deprecated. The blog post is talking about Rest API specifically, later on it states changes with FQL.
To access comments you need a valid access token that can view the top level object. Assuming this is just for comments on websites, a normal extended page access token should suffice by following scenario 5 explained at https://developers.facebook.com/roadmap/offline-access-removal/
https://graph.facebook.com/oauth/access_token?
client_id=APP_ID&
client_secret=APP_SECRET&
grant_type=fb_exchange_token&
fb_exchange_token=EXISTING_ACCESS_TOKEN
Exchange the short-lived user access token for a long-lived access token using the endpoint and steps explained earlier. By using a long-lived user access token, querying the [User ID]/accounts endpoint will now provide page access tokens that do not expire for pages that a user manages.
Then using a page access token from [User ID]/accounts you can pretty much hard code it in (you can create your own backend login tool, in the event that you invalidate the token one day or need to change it) via a server side language for example PHP using the PHP SDK
$facebook->setAccessToken('YOUR_PAGE_TOKEN');
So from here you can do an AJAX POST to the PHP page where the SDK is loaded
window.fbAsyncInit = function(){
FB.Event.subscribe('comment.create',
function(response) {
onCommentCreate(response.commentID);
}
);
function onCommentCreate(commentID) {
$.ajax({
type: 'POST',
url: 'createcomment.php',
data: {commentid:commentID},
success: function(result)
{
alert(result);
}
});
}
}
and request the comment information from there
<?php
if ($_SERVER['REQUEST_METHOD'] == 'POST' && isset( $_POST['commentid'] )) {
$commentid = $_POST['commentid'];
require 'facebook.php';
$facebook = new Facebook(array(
'appId' => 'APP_ID_HERE',
'secret' => 'APP_SECRET_HERE',
));
$facebook->setAccessToken('YOUR_PAGE_TOKEN');
$response = $facebook->api($commentid);
echo $response['from']['id'];
}
?>
References
https://developers.facebook.com/docs/graphapi/guides/comments/
https://developers.facebook.com/roadmap/offline-access-removal/
Related
On my site, I have some jquery which is making a post request from my login form to my API. Here is the jquery:
function doLogin()
{
var userEmail = $("#email").val();
var userPassword = $("#password").val();
$.post(
"https://api.linkenfest.co.uk/access/login/<?= $referrer ?>",
{
email: userEmail,
password: userPassword
}
).done(function( data ){
var status = data.data.status;
var referrer = data.data.referrer;
var message = data.data.message;
var session = data.data.session;
alert( message );
if( status == 200 ){
window.location.replace( referrer + "?session=" + session );
}
}
);
On returning a success, the response from my api looks like this:
{
"status": 200,
"data":{
"status": 200,
"referrer": "page.php",
"message": "Successful login",
"session": "o3vo1uram0k2mojmbd45pmicr2"
}
}
As you can see from my response, it includes the value obtained from php session_id()
I would like to find out if giving this information to the user opens up for a security vulnerability on the site.
The session ID value is used to start the session after window.location.reload
Thanks.
As others have already pointed out in comments, the client getting the session id is the standard way to manage sessions - how else could it send it in subsequent requests? But it does matter how the client receives it.
Note that the code above is very likely vulnerable.
"https://api.linkenfest.co.uk/access/login/<?= $referrer ?>" - This is reflected XSS if $referrer is the Referer header from the request. As it is in a Javascript context, it is not enough to simply html encode, you need javascript encoding.
window.location.replace( referrer + "?session=" + session ) - This technically is an open redirect to the referrer parameter. It is not easy to exploit though. The best practice would still be to validate it (preferably on the server - which might already be in place).
Weak session management, session id is available to Javascript. While in some cases this is acceptable, accepting this risk should be a conscious decision. A session id should normally (and traditionally) be set in an httpOnly cookie. One exception when this can't be done is when a token needs to be sent to a different origin. But in that case it's not a session id semantically, and a proper SSO solution should be in place.
A session id is sensitive information, and as such, should not be sent in the url. The problem with the url is that it gets logged in multiple places (the user's browser will remember it and save it to disk, it will get logged on intermediate proxies, and in the target server's logs. These are all places from where an attacker might be able to get it. It's best to not send sensitive information in the url, but either in a header (a cookie is also just a header), or the request body is also ok if needed.
The destination url (api.linkenfest.co.uk) is vulnerable to login csrf if it allows you to log in this way.
Also looking at this from a broader perspective, what I guess you are doing is using the api as an identity provider to log your user in. For this purpose you should use a standard protocol like OpenID Connect, and not reinvent the wheel, because as you can see above, it is not at all straightforward.
I can't seem to find this answered anywhere on SO or even Google - I have an oauth-signed call to the Flickr upload API, and following the docs I've signed the POST operation the usual oauth way (but without the photo data). For testing purposes I've only passed along a title and the photo data, which means I end up a var flickrURI that contains the following url for POSTing to:
https://api.flickr.com/services/upload/
? format=json
& oauth_consumer_key=...
& oauth_nonce=2e57b73fec6630a30588e22383cc3b25
& oauth_signature_method=HMAC-SHA1
& oauth_timestamp=1411933792346
& oauth_token=...
& title=test
& oauth_signature=O7JPn1m06vl5Rl95Z2in32YWp7Q%3D
(split over multiple lines for legibility in this question, the actual URL has no whitespacing around the ? and & for obvious reasons).
The oauth signing itself is quite correct, and code used for accessing the not-upload-API all over the place with correct behaviour, so it seems pretty much impossible for that to get the signing wrong, other than perhaps signing with "not enough data" or perhaps signing with "too much data".
The auth signing first forms the auth query string, in this case:
oauth_consumer_key=...
&oauth_nonce=60028905f65cf9d7649b3bce98f718f8
&oauth_signature_method=HMAC-SHA1
&oauth_timestamp=1411939726691
&oauth_token=...
&title=test
which is then used to form the verb + address + encoded base string:
POST&https%3A%2F%2Fapi.flickr.com%2Fservices%2Fupload%2F&oauth_consumer_key%3D...%26oauth_nonce%3D60028905f65cf9d7649b3bce98f718f8%26oauth_signature_method%3DHMAC
-SHA1%26oauth_timestamp%3D1411939726691%26oauth_token%3D...%26title%3Dtest
This is then HMAC-SHA1 digested using the Flickr and oauth secrets:
function sign = (data, key, secret) {
var hmacKey = key + "&" + (secret ? secret : ''),
hmac = crypto.createHmac("SHA1", hmacKey);
hmac.update(data);
var digest = hmac.digest("base64");
return encodeURIComponent(digest);
}
And for GET requests, this works perfectly fine. For POST requests things seem to be difference, despite the docs not explain which part is supposedly different, so I the tried to use the Nodejs request package to perform the POST action in what seemed a normal way, using:
uploadOptions = {
oauth_consumer_key = auth.api_key,
oauth_nonce = auth.oauth_nonce,
oauth_timestamp = auth.oauth_timestamp,
oauth_token = auth.access_token,
oauth_signature_method = "HMAC-SHA1",
title: title,
photo: <binary data buffer>
};
flickrURL = formSignedURL(auth);
request.post({
url: flickrURI,
headers: {
"Authorization": 'oauth_consumer_key="...",oauth_token="...",oauth_signature_method="HMAC-SHA1",oauth_signature="...",oauth_timestamp="...",oauth_nonce="...",oauth_version="1.0"'
},
multipart: [{
'content-type': 'application/json',
body: JSON.stringify(signOptions)
}]
},function(error, response, body) {
console.log("error");
console.log(error);
console.log("body");
console.log(body);
}
);
which yields a body that contains:
<?xml version="1.0" encoding="utf-8" ?>
<rsp stat="fail">
<err code="100" msg="Invalid API Key (Key has invalid format)" />
</rsp>
As the oauth signing doesn't really give me a choice in which API key to provide (there is only one) and the signing works just fine for the not-upload API, I can't figure out what this error message is trying to tell me. The key is definitely the right format because that's the format Flickr gives you, and it's the correct value, because it works just fine outside of uploading. I also made sure to get the oauth token and secret for that key with "delete" permission (widest possible permissions) so the included access token and access token secret should pass the "does this token for this key have permissions to write" test.
What obvious thing am I missing here that's preventing the upload to go through?
It looks like you're using the https://up.flickr.com/services/upload/ endpoint, which uses the old authentication scheme.
For OAuth, it should be https://api.flickr.com/services/upload/. Make sure the endpoint is included in signing process.
I don't think it's documented anywhere, but I remember having same issue a while back.
It turns out adding the data as request.post multipart information isn't good enough, and will make the Flickr API throw an "Invalid API Key (Key has invalid format)" error instead of saying what's actually wrong. The following request call, with exactly the same data, works:
var uploadOptions = ...
var flickrURL = ...;
var req = request.post(flickrURL, function(error, response, body) {
callback(error, body);
});
var form = req.form();
uploadOptions.photo = fs.createReadStream(...);
Object.keys(photoOptions).forEach(function(prop) {
form.append(prop, photoOptions[prop]);
});
Despite not making all that much sense call wise (why would the POST not already be done by the time we call form = req.form()?) this is request's "proper" way to send the POST payload over the wire, and makes the Flickr API process the photo upload just fine.
I'm using Facebook's Javascript SDK, and I need a way to let users post to a specific page impersonating one of the pages they own. Posting as the user works perfectly, but I can seem to get this right:
var data = {
access_token: access,
message: "Hello"
};
FB.api("/" + page_id + "/feed", "post", data, function(response) {
console.log(response);
});
page_id is the numeric ID of the specific page I want to post to, and access is the access_token retrieved from the "me/accounts" api, using the manage_pages authorization.
The output I recieve from the response variable is the following object:
error: Object
code: 200
message: "(#200) Posts where the actor is a page cannot also include a target_id"
type: "OAuthException"
I couldn't find the answer to this anywhere. Is this just not possible? It can't be.
quick googling suggests it's not possible:
https://stackoverflow.com/questions/9706873/post-on-wall-fan-page-to-a-fan-page-that-likes
How to Post with Application Name
also, take a look at the old REST API documentation: https://developers.facebook.com/docs/reference/rest/stream.publish/
target_id
Note: If you specify a Page ID as the uid, you cannot specify a
target_id. Pages cannot write on other users' Walls.
Note: You cannot publish to an application profile page's Wall.
In the authentication flow documentation here it mentions the CODE which is returned upon oAuth authentication.
Is this required for the Javascript SDK or is this handled automatically in the background in this code?
By "is this required?" I mean, do I have to handle this code to verify the authenticity of the request, or does the JavaScript SDK use the code automatically to gain the access_token.
The documentation explains the client side flow, and how to get the access token using the 'code' so until now. I've been assuming that the SDK manages this automatically in the background, because it produces an access code as response.authResponse.accessToken.
FB.login(function(response) {
if (response.authResponse) {
// User is logged in to Facebook and accepted permissions
// Assign the variables required
var access_token = response.authResponse.accessToken;
var fb_uid = response.authResponse.userID;
alert(dump(response.authResponse));
// Construct data string to pass to create temporary session using PHP
var fbDataString = "uid=" + fb_uid + "&access_token=" + access_token;
// Call doLogin.php to log the user in
$.ajax({
type: "POST",
url: "ajax/doLogin.php",
data: fbDataString,
dataType: "json",
success: function(data) {
// Get JSON response
if (data.result == "failure")
{
alert(data.error_message);
window.location.reload();
return false;
}
else if (data.result == "success")
{
window.location.reload();
return true;
}
},
error: function() {
return false;
}
});
} else {
// user is not logged in and did not accept any permissions
return false;
}
}, {scope:'publish_stream,email'});
I would like to know, because I want to ensure that my code is secure.
From the documentation
With this code in hand, you can proceed to the next step, app authentication, to gain the access token you need to make API calls.
In order to authenticate your app, you must pass the authorization code and your app secret to the Graph API token endpoint at https://graph.facebook.com/oauth/access_token. The app secret is available from the Developer App and should not be shared with anyone or embedded in any code that you will distribute (you should use the client-side flow for these scenarios).
If you plan on using the FB.api function to make calls to their Graph API, then you need the code to get the access token. But if you only need to authenticate the user, then what you have will do that just fine.
I'm building a website that makes use of Facebook connect. I'm authenticating users client-side with the javascript SDK and calling an AJAX method on my server every time a user logs in to check if the user is known to my app, and if the user is new to store their FBID in my database to register them as a new user.
My question is: Can the access token returned by Facebook to the Javascript SDK be used server-side (with the PHP SDK for example)? Can I send the access token string to the server via an AJAX call, store it in my database (along with a timestamp so I know how long it's valid for) and then use it to make calls to the graph API server-side? Is this even a logical thing to do?
Yes, this should work. Look at this question: How to properly handle session and access token with Facebook PHP SDK 3.0?
This is a workaround for the old JS and new PHP SDK. In my app I send the access token generated by the JS SDK via a form to my PHP. I have no doubts that this also works by sending the access token via ajax!
Using Jquery:
//Set an error message
var oops = ("Put your something went wrong message here.");
//Function to post the data to the server
function save(uid, accessToken){
$.post("../foo/bar", { uid: uid, access_token: accessToken, etc, etc }, function(data){
alert("Successfully connected to Facebook.");
location.reload();
}, "text");
}
function handler(x){
if (x.authResponse){
var token = x.authResponse.accessToken;
var uid = x.authResponse.id;
FB.api("/me/accounts", {access_token: token},
function(response){
if(response.data.length == 0) {
//Regular facebook user with one account (profile)
save(uid, token);
}else{
//Handle multiple accounts (if you want access to pages, groups, etc)
}
});
}else{
alert(oops);
}
}
FB.login(handler, {scope: 'The list of permissions you are requesting goes here'});
Any improvement suggestions are always appreciated.