Facebook page tab app session across subpages PHP SDK 4 - facebook

See the full original question further down
Using the latest Facebook PHP SDK 4.4.0, in my main app page I can do the following to get a user id etc.
<?php
FacebookSession::setDefaultApplication(APP_ID, SECRET);
$helper = new FacebookRedirectLoginHelper( PAGE_URL );
$pageHelper = new FacebookPageTabHelper();
$session = $pageHelper->getSession();
echo '<p>You are currently viewing page: '. $pageHelper->getPageId() . '</p>';
// get user_id
echo '<p>User Id: ' . $pageHelper->getUserId() . '</p>';
// **depcrecated** get like status - use for likegates
echo '<p>You have '. ( $pageHelper->isLiked() ? 'LIKED' : 'NOT liked' ) . ' this page</p>';
// get admin status
echo '<p>You are '. ( $pageHelper->isAdmin() ? 'an ADMIN' : 'NOT an ADMIN' ) . '</p>';
?>
This does not work on sub pages of my app ... Why is the session (and amongst other things, the signed request) lost? How can I get them back and how can I get methods such as getUserId() from the the FacebookPageTabHelper to continue to work on sub pages?
full original question
I'm fairly new to Facebook app development and I'm having problems with session management and I just can't seem to be able to wrap my head around it. Of course it doesn't help that the official documentation is almost useless.
My problem is that the page session get lost when moving away from the apps main page to a subpage within the Facebook page tab app iframe.
I use the following PHP code to obtain the session and user id on the main (initial) app page and it works great:
<?php
FacebookSession::setDefaultApplication(APP_ID, SECRET);
$helper = new FacebookRedirectLoginHelper( PAGE_URL );
$pageHelper = new FacebookPageTabHelper();
$session = $pageHelper->getSession();
?>
But it doesn't work on sub pages :( when a user clicks on a menu item (or any other link inside the app/iframe), the session goes bye bye. Which is not ideal as I need the user id of the user to track whether or not that user has completed certain actions. Of course I could send the ID along with every request, but there must be a way to have a persisting session, no?
Is there a way to retrieve the session on a sub page in PHP? If so, how? Or do I have to load additional content using javascript? And how would that work, if I can't keep the session between requests and therefore have no way of identifying which user a request came from? How do others handle this?
What I'd like to avoid is to write my own user session management, which would solve the problem but is simply not in the budget and I was hoping I could work with what Facebook already had on offer. Especially since my app doesn't require user information/permissions of any kind.
Thanks a lot in advance for any info on this topic, greatly appreciated, going in circles here.
Edit to clarify: I thought of just saving the Facebook session in a PHP session cookie, but how would I use that to reconnect with Facebook after changing the page?

I finally managed to solve this problem. I'm not sure whether this is considered the right way or can even be a recommended way of doing this, but it works and since time is of the essence, I don't have much of a choice.
If anybody has any further ideas or suggestions, please comment.
Here's how I did it:
// store the signed request
if(isset($_REQUEST['signed_request'])) {
$_SESSION['signed_request'] = $_REQUEST['signed_request'];
} elseif($_SESSION['signed_request']) {
$_REQUEST['signed_request'] = $_GET['signed_request'] = $_POST['signed_request'] = $_SESSION['signed_request'];
}
// assign the stored signed request to REQUEST, GET and POST vars (the unsavory bit, imo)
$_REQUEST['signed_request'] = $_GET['signed_request'] = $_POST['signed_request'] = $_SESSION['signedRequest'];
FacebookSession::setDefaultApplication(APP_ID, APP_SECRET);
$accessToken = APP_ID . '|' . APP_SECRET;
$this->session = new FacebookSession($accessToken);
$pageHelper = new FacebookPageTabHelper();
$isAdmin = ($this->pageHelper->getPageData('admin')) ? $this->pageHelper->getPageData('admin') : 0;
// get pade id
echo '<p>You are currently viewing page: '. $pageHelper->getPageId() . '</p>';
// get user_id
echo '<p>User Id: ' . $pageHelper->getUserId() . '</p>';
// get admin status
echo '<p>You are '. ( $isAdmin ? 'an ADMIN' : 'NOT an ADMIN' ) . '</p>';

Related

Facebook API 2.2: Graph error when trying to post a link

The goal of the script is to post automatically, on my profile and on my groups, the link to a specific page, once the content is created.
But all what I got when I launch the test script here below, is this returned string:
Graph returned an error: An active access token must be used to query information about the current user.
In fact, the token related to the app was given by FB Developers site.
I tried also the combination:
634060413388093|9ed702cc524a1cbb59ca1fb7a17839f1
But I still get the same return
<?php
$path = getcwd();
require_once $path . '/include/Facebook/autoload.php';
$fb = new Facebook\Facebook([
'app_id' => '634060413388093',
'app_secret' => '9ed702cc524a1cbb59ca1fb7a17839f1',
'default_graph_version' => 'v2.2', ]);
$linkData = [
'link' => 'http://www.example.com/page',
'message' => 'here the new page on my site.',
];
try {
// Returns a `Facebook\FacebookResponse` object
$response = $fb->post('/me/feed', $linkData, '634060413388093|bnTyPyRtsZSLHoc1B1w772cz3BU');
} catch (Facebook\Exceptions\FacebookResponseException $e) {
echo 'Graph returned an error: ' . $e->getMessage();
exit;
} catch (Facebook\Exceptions\FacebookSDKException $e) {
echo 'Facebook SDK returned an error: ' . $e->getMessage();
exit;
}
$graphNode = $response->getGraphNode();
echo 'Posted with id: ' . $graphNode['id'];
?>
You need to use a user access token to read the feed of a user. 'me' refers to the user that is associated with the user access token. An app access token is not associated with any specific user.
This leads to the second point, which is much more important. You should never reveal your app secret, especially not on Stack Overflow or anywhere public. You should hide that part of your SO Question, or better yet, create a new FB application.
Ok I found the way in this moment. I tried any example andy help, any suggestion but it didn't work out.
Thus, I post it hoping it helps somebody else with my same issue:
The access token, is not app_id|app_secret but it's obtained in another way:
To get the correct access otken, one should go to:
https://developers.facebook.com
under "Tools & Support" select "Graph API Explorer"
On the upper right corner, there is a drop down menu called "Application".
Click and select the created application.
Select "Access Token" shown, and copy/paste it in the field aside $linkDAta variable:
$response = $fb->post('/me/feed', $linkData, '{access-token}');
At that point, launching the script as it is, it worked out :)
EDIT:
If you need to post in groups, just change the '/me/feed' with '{group_id}/feed'

The "state" param from the URL and session do not match

In facebook documantion
require('include/facebook/autoload.php'); //SDK directory
$fb = new Facebook\Facebook([
'app_id' => '***********',
'app_secret' => '***********************'
]);
$helper = $fb->getRedirectLoginHelper();
$permissions = ['email', 'public_profile']; // optional
$loginUrl = $helper->getLoginUrl('http://www.meusite.com.br/login-callback.php', $permissions);
When direct it to the url $loginUrl, the return is:
Facebook SDK returned an error: Cross-site request forgery validation failed. The "state" param from the URL and session do not match
I had the same error.
The problem occurred because I did getLoginUrl(...) before getAccessToken()
So rid of getLoginUrl(...) in redirected URL and code should works.
I had the same issue and for me that error was occurring because I did not put session_start(); in my login.php page code before calling getLoginUrl(..) and also at the top of login-callback.php page.
Just put session_start(); in your "login" page and "login-callback" page and it will work surely just like it is working for me now.
There could be 2 reason for this error:
you didn't call session_start(); before getLoginUrl call
You executed getLoginUrl again in login-callback.php, so state value regenerated and mismatched with the redirected value
Possible Fixes : I used the following configuration settings .
Enable WebAuthLogin under the advanced tab . Provide the url in the WebAuthLogin settins as same as that you provide in $loginUrl ;
For example if you use $loginUrl as https://example.com/ use that same in the WebAuthlogin Url
$loginUrl = $helper->getLoginUrl('https://example.com/', $permissions);
This problem occures also in case that you generate 2 or more login links on the same page (e.g. one for login and other for registration - even both point to the same url, they have just different labels).
Facebook SDK creates/updates $_SESSION[FBRLH_state] for each new generated loginURL. So if there are 2 generated URLs (using $helper->getLoginUrl()) then the $_SESSION[FBRLH_state] is 2-times rewritten and valid only for the last generated URL. Previous login URL becomes invalid. It means that it is not possible to generate 2 valid loginURLs. In case that 2 same URLs are generated then return the first one and avoid call of Facebook SDK for generation of second one.
I had the same problem.
The reason for this error is because --->
When "$helper->getLoginUrl" calls, it create a session variable "FB_State", and this is something to FB uses to match the token. Every-time getLoginUrl calls, it create new state. Then after user authorized and redirect back, if you codes cannot detect this event and re-run "$helper->getLoginUrl", then this error will occur.
The solution ->
refine your coding, stop run "$helper->getLoginUrl" again if authorized.
if you already rerun, then set the session variable for the token to NULL if you have, then User can re-authorize again.
when user tries re-authorize, they can remove the authorized APP once or you need to generate new link with "$helper->getReRequestUrl"
Yet, token has be called by "getAccessToken()" before the "$helper->getLoginUrl" or "$helper->getReRequestUrl" runs.
Good Luck!!!!!
Finally, looking into FB code, I discovered that the problem "Cross-site request forgery validation failed. Required param “state” missing" and similars are caused by PHP variable $_SESSION['FBRLH_state'] that for some "strange" reason when FB call the login-callback file.
To solve it I store this variable "FBRLH_state" AFTER the call of function $helper->getLoginUrl(...). Is very important to do only after the call of this function due to is inside this function when the variable $_SESSION['FBRLH_state'] is populated.
Below an example of my code in the login.php:
$uri=$helper->getLoginUrl($uri, $permissions);
foreach ($_SESSION as $k=>$v) {
if(strpos($k, "FBRLH_")!==FALSE) {
if(!setcookie($k, $v)) {
//what??
} else {
$_COOKIE[$k]=$v;
}
}
}
var_dump($_COOKIE);
And in the login-callback.php before calling all FB code:
foreach ($_COOKIE as $k=>$v) {
if(strpos($k, "FBRLH_")!==FALSE) {
$_SESSION[$k]=$v;
}
}
Last, but not least, remember also to include code for PHP session so..
if(!session_id()) {
session_start();
}
...
...
...
...
<?php session_write_close() ?>
I hope this response can help you to save 8-10 hours of work :)
Bye, Alex.
This issue was a bit confusing for me, because I had to change a line at the facebook src file:
src/Facebook/Helpers/FacebookRedirectLoginHelper.php
at the function: "validateCsrf" like this:
if ($result !== 0) {
throw new FacebookSDKException('Cross-site request forgery validation failed. The "state" param from the URL and session do not match.');
}
And change it into:
if ($result === 0) {
throw new FacebookSDKException('Cross-site request forgery validation failed. The "state" param from the URL and session do not match.');
}
I don't know if this makes a violation to the facebook SDK security, so I truly opened to any exlanation or recommendation for this answer.
You may also make the following changes at the facebook app manager:
add your site and callback-url into your facebook app account at:
setting->advanced:Valid OAuth redirect URIs
Don't forget to add another url with slash (/) at the end of each url and check all 4 checkboxes at Client OAuth Settings.
I had the same error. Are you using 1 file or 2? I was trying to get by using 1 file but my error was resolved when I split into login.php & fb-callback.php as the documentation recommended. My sessions were being re-written so the state was never saved properly.
Good luck!
Happens when the session in missing a needed variable.
might be caused by several things.
In my case I left the "www" out of the callback URL
You could actually be parsing the data from another domain... for example:
website.com is different from www .website.com
If you're parsing data from http ://website.com/login.php to http://www.website.com/fb-callback.php this would be a cross-domain problem and the error you are receiving would be because of that....
http ://website.com and http ://www.website.com are the same but the script identifies them as different..... hope that gives insight to the problem.

Facebook Friend List Import using fboauth, see for local account and display using views

I am using fboauth for enabling login with facebook for the website. Here is an overview of how I achieve the functionality:
When the user clicks on the facebook login button on the website, he or she is taken to a facebook login page. After logging in with facebook, the user is taken to the app authorization page where the user asks for permission to connect with the app. Once necessary permissions are granted, a local account (account at my website) is automatically created for the user and the user is brought back to a welcome page to set password. The user won't even have to verify their email address. On subsequent visits, when the user clicks on the login link, he/she is taken to the same facebook login page where they supply their login credentials. On successful login, they are brought back to the website. So far, everything works fine with the fboauth module.
What I am trying to achieve now is a functionality similar to what is found with the fbconnect module. The user is provided with a block/page where the user can import the list of his friends who has authorized with the app, and see the links to their local accounts (accounts at the website). How to achieve this functionality? The fboauth module has its own API which can be utilized. Here is what I already have, written using the API of fboauth.
<?php
module_load_include('inc', 'fboauth', 'includes/fboauth.fboauth');
module_load_include('module', 'fboauth', 'fboauth');
module_load_include('php', 'fboauth', fboauth.api');
module_load_include('inc', 'fboauth', 'includes/fboauth.field');
module_load_include('inc', 'fboauth', 'includes/fboauth.pages');
module_load_include('inc', 'fboauth', 'includes/fboauth.profile');
/**
* Implements hook_menu().
*/
function mymodule_menu() {
$items['my-friends'] = array(
'title' => t('Your Friends'),
'page callback' => 'friend_import',
'access callback' => TRUE;
);
return $items;
}
function friend_import() {
$result = fboauth_graph_query('me/friends?fields=id', $access_token);
drupal_set_message(t('Import complete!'));
$accounts = array();
$output = "";
foreach($result->data as $fbuid){
$accounts[] = user_load(fboauth_uid_load($fbuid->id));
$output = l($account->name, "/user/" . $account->uid);
}
dpm($accounts);
return $output;
}
This will import all the friends of a user who have authenticated for the app as an object into $result (having name and user id). However, what I am finding it difficult is to display those names with the user's corresponding local accounts (accounts at the website). This is because of my lack of knowledge in php. What I am looking for is here is the exact lines of code that can be inserted after $result recieves its value so that the names of the users are displayed along with the links to their profile pages at the website.
Ok, so, if you look at the fbconnect module, you will see that there is a table that contains both the fid and uid (FB's and Drupal's). There is also a function you can user, to save you from the work of writing the query yourself:
/**
* Load a Drupal User ID given a Facebook ID.
*/
function fboauth_uid_load($fbid) {
$result = db_query("SELECT uid FROM {fboauth_users} WHERE fbid = :fbid", array(':fbid' => $fbid));
$uid = $result->fetchField();
return $uid ? (int) $uid : FALSE;
}
So, in order to get all friends, you would first need to call the the function that returns user's friends in FB:
$result = fboauth_graph_query('me/friends?fields=id', $access_token);
Then, iterate through that $result and get the local data:
$accounts = array();
foreach($result->data as $fbuid){
$accounts[] = user_load(fboauth_uid_load($fbuid->id));
}
So far, I think you had already figured this out.
Next thing we do, depends on how you want the module to behave. If you need a custom page, with an url, implement hook_menu to create that page:
/**
* Implements hook_menu().
*/
function mymodule_menu() {
$items = array();
$items['desired/path'] = array(
'title' => t('My friends'),
'page callback' => 'fb_friends', <-- This function you need to create now
....
);
function fb_friends() {
$result = fboauth_graph_query('me/friends?fields=id', $access_token);
$accounts = array();
$output = "";
foreach($result->data as $fbuid){
$accounts[] = user_load(fboauth_uid_load($fbuid->id);
$output .= l($account->name, "/user/" . $account->uid) . </br>;
}
return $output;
}
For better formatting, instead of building the $output yourself, you would call theme_table, that does it for you: https://api.drupal.org/api/drupal/includes!theme.inc/function/theme_table/7
Lastly, I recommend saving the relationships to a user and his friends in a local table, so next time you don't have to go to FB. I think you can take it from the example above. If your problem is showing the friends, it will do, although not very efficient because of going to FB everytime. Hope it helps.
You need either a user id or name or email to match Drupal's corresponding fields. These three fields are unique for a Drupal site. Unless the query to FB return any of those, you won't be able to map a FB user to a Drupal user.
Provided that the name or the user id are the same for drupal app, you can do:
$output = "";
foreach ($result as $u) {
$output .= $u->name , "<br>"; //or $u->id for the id
}
return $output;
This will work if A) $result contains a list of objects, B) Those are the exact field names.
The other issue is that if you want a custom page with the output, you need to implement the hook_menu so you can provide such custom page in your module.
I recommend you install the Devel module for easily inspection your data. After you install it and enable it, just call dsm($result) from your code, so you have a nice view of what is in the $result variable.
And before actually building that into a module, try simply adding a basic Drupal page, set the format to PHP and write that code, so you can test easily and then move that into the module.
So to recap, the first thing to do is to find out what comes in that variable and in which structure. Then, if any of those fields have the same value for the local users, just write the hook_menu and provide a custom page within your module.
Provided that you have, say, the user id (Drupal's Id) you can build a link to the user account by printing $output .= '' . $name . '<br>'; and then return $output;
If you give me the $result content here, I might come up with a better solution.

Session in Laravel does not work inside the Facebook Application Iframe

I have a facebook app that runs inside an iframe. After the user allow permissions from the app I'm expecting facebook to send me $_REQUEST variables that I use to retrieve user data and it is working well. The problem is after I put the users data in Laravel Session using Session::put() and then Session::save(), on the next request the session is gone. So my app cannot retrieve the Session anymore. I really don't know why during the test all of my browsers work, (Safari, Firefox, Chrome). So I put some error checking to email me everytime there is an Session Error and still I got 30+ emails per day telling that Chrome has no Session, and some users were posting about the App returning Error. I'm really struggling for this problem for days now. I've implemented some fix adding P3P headers and adding favicon on my site, still no avail.
Here is my code:
Route::filter('before', function()
{
// Do stuff before every request to your application...
header('P3P:CP="NOI DEV PSAi COM NAV OTR STP DEM HONK IDC DSP COR ADM DEVi TAIi CAO PSA PSD IVAi IVDi CONi HIS OUR IND CNT"');
//safari 3rd party fix cookie fix
if(isset($_SERVER['HTTP_USER_AGENT'])) {
if (! count($_COOKIE) > 0 && strpos($_SERVER['HTTP_USER_AGENT'], 'Safari')) {
if(strpos($_SERVER['HTTP_USER_AGENT'], 'Chrome') == false) {
session_start();
$page_url = Config::get('custom.fb_page');
if (isset($_GET["start_session"]))
die(header("Location:" . $page_url));
$sid = session_id();
if (!isset($_GET["sid"]))
{
if(isset($_POST["signed_request"]))
$_SESSION["signed_request"] = $_POST["signed_request"];
die(header("Location:?sid=" . $sid));
}
if (empty($sid) || $_GET["sid"] != $sid)
die('<script>top.window.location="?start_session=true";</script>');
}
}
}
}
}
//and the code that sets Laravel Session
Route::any('tab/(:any?)', function ($res = null)
{
//$response = some_function_to_get_token($_REQUEST['signed_request']);
if($response && isset($response['oauth_token']))
{
Session::put('my_session_for_token', $response['oauth_token']);
Session::save();
$redirect = Redirect::to('my_awesome_page');
}
else
$redirect = Redirect::to('permission');
}
EDIT
I've already tried setting $_SESSION and commenting out Session::put() but it doesn't set my $_SESSION at all. Next thing I tried was uploading a sessionchecker.php script and tried it using the browser.
heres the code for session check:
<?php
error_reporting(E_ALL);
ini_set('display_errors', '1');
session_start();
if (isset($_SESSION['views']))
$_SESSION['views'] = $_SESSION['views'] + 1;
else
$_SESSION['views'] = 0;
echo '<pre>';
var_dump(session_id()); // I should stay the same
var_dump($_SESSION); // I should start at 0 and increase
echo '</pre>';
and it working as expected, Im suspecting that it has something to do with Redirect::to().
P.S. I've already tried configuring Laravel to use Cookie and File but still the error occurs.
This is not a Laravel problem. This is a classic Safari/Internet explorer 3rd party cookie problem.
Try googling for "safari cookie fix" or check out some of these questions here, for example:
Facebook Iframe App with multiple pages in Safari Session Variables not persisting

integrating with Facebook like

I'd like to integrate the "like" button into my site, but I cannot make sense of the information available on the web. I read this article, which was in another stackoverflow article, but have some questions: http://www.saschakimmel.com/2010/05/how-to-capture-clicks-on-the-facebook-like-button/. I've also posted this question before and the answers I received really had nothing to do with what I was asking. My original question was asked here: Facebook Integration into website.
I've setup a "Page" already in facebook, and from what I understand in the link above, you need to setup an "App" to get an API key. What I don't understand though is that if I use this API Key, it's going to be pointing to my newly created "App", which has no fan base. How do I link this API Key, (or setup another key through the page admin), so I can have users "like" the real facebook page?
I want to run some javascript functions the moment a user likes the page, but I'm also a little confused on what API functions call, and whether these return a true/false value? I only really want to run these js functions if the user has not already liked the page..
Hope this all makes sense, would love any explanations you have to offer to point me in the right direction.
From what I can see, the answers on your other question cover most of what you need to know. The one thing I notice is your comment:
"I am attempting to set a promo code in the background when someone
"likes" the page"
There are some tricky terms and conditions surrounding this. Have a look here before continuing: https://developers.facebook.com/docs/guides/policy/examples_and_explanations/Rewarding_Users/
If you start off with adding the like button, then separately you will need to check each logged in facebook user to see if they are connected with your page. You can do this using the api call to get their likes, and checking for your page id in the response:
FB.api('/me/likes', function(response) {
console.log(response);
});
If you find a match, proceed with your promotion, else show the like button.
What you're trying to do is totally possible, although I usually do that calculation server-side. You may be able to do it via the Javascript SDK, using the basic concept below. Check out this link: http://fbmhell.com/2011/06/facebook-like-gating-in-iframe-tabs/
The basic overview is this:
You create a Page
You create an app for your promo tab
You add the app tab to your page
When the user hits your app tab on your page, Facebook will return a signed request to you.
You can parse out that signed request using a function like this:
function grokSignedRequest() {
if (isset($_REQUEST['signed_request'])) {
$encoded_sig = null;
$payload = null;
list($encoded_sig, $payload) = explode('.', $_REQUEST['signed_request'], 2);
$sig = base64_decode(strtr($encoded_sig, '-_', '+/'));
$data = json_decode(base64_decode(strtr($payload, '-_', '+/'), true));
return $data;
}
return false;
}
As mentioned in that article, if you do a print_r() on that signed request after it's been run through the function provided, you’ll see something like this:
stdClass Object
(
[algorithm] => HMAC-SHA256
[issued_at] => 1307627872
[page] => stdClass Object
(
[id] => 116633947708
[liked] => 1
[admin] => 1
)
[user] => stdClass Object
(
[country] => us
[locale] => en_US
[age] => stdClass Object
(
[min] => 21
)
)
)
From there, you can access the liked parameter, and display content based on whether or not the user has liked the page.
// call the function to parse the signed request
$sr_data = grokSignedRequest();
// check like status
if ($sr_data->page->liked==1) {
echo 'you are a fan';
} else {
echo 'you are not a fan.';
}
// check admin status
if ($sr_data->page->admin==1) {
echo '<li>Dude, you are an ADMIN! BADASS!';
}