How to handle Facebook's signed_request for iFrame Canvas applications? - facebook

I'm developing an iFrame Canvas application for Facebook using CakePHP, its Auth component, WebTechNick's Facebook plugin and OAuth for canvas pages (I've enabled this in the Facebook Developer app options). I would like users to be able to use the application after adding it to their profile (by requesting email and publish_stream permissions) by visiting http://apps.facebook.com/myapp/ or as a tab in their profile.
Requesting permissions is not the problem. The user is redirected to the permissions request page and then redirected to a callback method which requests an access_token, as per this tutorial.
After this callback the user is redirected back to http://apps.facebook.com/myapp/ which shows their personal index page. This is also where the problems start. As soon as the aforementioned URI is loaded, the browser asks for a form resubmission, this happens every time I reload http://apps.facebook.com/myapp/. This is the case because Facebook wants to pass the (expected) signed_request parameter and I'm wondering what to do with it. It's not an empty variable, so do I need another validation method or redirect, perhaps?
How should I handle the procedure for the signed_request parameter and, more importantly how to get rid of this form resubmission dialog?
Some of my methods, they might be a bit of a mess due to all the experimentation of the past day.
beforeFilter, login and callback methods, in my UserController.php:
function beforeFilter() {
parent::beforeFilter();
if (empty($this->permissions)) {
$this->Auth->allow('login', 'logout', 'callback');
}
}
function login() {
$session = $this->facebook->getSession();
$login_url = 'https://graph.facebook.com/oauth/authorize?client_id=' . FACEBOOK_APP_ID . '&redirect_uri=' . MY_APP_URL . '/users/callback/&type=user_agent&&display=page&scope=' . FACEBOOK_APP_PERMISSIONS;
if($session){
try {
$uid = $facebook_client->getUser();
$me = $facebook_client->api('/me', $params);
print($me);
} catch (FacebookApiException $e) {
error_log($e);
}
} else {
$this->set('authorise', true);
$script = '$(document).ready(function() { facebookRequestPermissions("'.$login_url.'");});';
$this->set('script', $script);
}
}
function callback() {
function callFb($url, $params) {
$ch = curl_init();
curl_setopt_array($ch, array(
CURLOPT_URL => $url,
CURLOPT_POSTFIELDS => http_build_query($params),
CURLOPT_RETURNTRANSFER => true,
CURLOPT_VERBOSE => true
));
$result = curl_exec($ch);
curl_close($ch);
return $result;
}
$params=array('client_id'=>FACEBOOK_APP_ID, 'type'=>'client_cred', 'client_secret'=>FACEBOOK_APP_SECRET);
$url = "https://graph.facebook.com/oauth/access_token";
$access_token = callFb($url, $params);
$access_token = substr($access_token, strpos($access_token, "=")+1, strlen($access_token));
if ($access_token) {
$this->redirect(FACEBOOK_APP_URL);
} else {
echo 'An error has occurred';
}
}
The JavaScript in the login method refers to this jQuery function, the Facebook JavaScript SDK is initialised in $(document).ready():
function facebookRequestPermissions(login_url) {
FB.getLoginStatus(function(response) {
if (response.status !== 'unknown') {
top.location.href=login_url;
}
});
}
The JavaScript function should only fire when a user is logged in, if not, a different landing page is shown.
I use some methods in an overall AppControler:
class AppController extends Controller {
var $components = array('RequestHandler', 'Session', 'Auth', 'Facebook.Connect');
var $helpers = array('Form', 'Time','Html','Javascript', 'Session', 'Facebook.Facebook');
protected $facebook;
protected $permissions;
private $user;
function beforeRender() {
//Save the username if it isn't already present
if ((int)$this->Auth->user('id') != '' && (string)$this->Auth->user('username') == '') {
$data = array('id' => (int)$this->Auth->user('id'), 'username' => (string)$this->user['username']);
$this->loadModel('User');
$this->User->save($data);
}
if (!empty($this->user) && !empty($this->permissions)) {
$this->set('currentUser', $this->Auth->user());
}
}
function beforeFilter() {
$this->Auth->autoRedirect = false;
$this->Auth->loginAction = array('controller' => 'users', 'action' => 'login');
$this->Auth->loginRedirect = array('controller' => 'users', 'action' => 'index');
$this->Auth->logoutRedirect = array('controller' => 'users', 'action' => 'login');
App::import('Lib', 'Facebook.FB');
$this->facebook = new FB();
$this->user = $this->facebook->api('/me');
$this->permissions = $this->facebook->api('/me/permissions');
}
}
EDIT:
This only seems an issue with Firefox. Chrome doesn't display the dialog, but instead does a silent refresh after which the signed_request parameter is empty, strangely enough. This isn't the case with Firefox, where the signed_request parameter remains the same after every prompted refresh (unless the iFrame content is cached), which is looping infinitely, it seems.
EDIT 2:
Still struggling with this, but I ended up disabling the OAuth 2.0 for Canvas option in the Facebook Developer application, which has resolved the form resubmission issue. Of course this is not a real solution, because OAuth 2.0 is becoming mandatory for canvas application on Facebook, I believe.

Since I can't test the whole thing I am not sure if this is right, but on the first sight your JavaScript function looks strange to me. It looks like you always redirect to the login url, although the user gave permission.
Refering to Facebook JavaScript SDK, the function should look like this:
function facebookRequestPermissions(login_url) {
FB.getLoginStatus(function(response) {
if (!response.session) {
top.location.href=login_url;
}
});
}
or, if you want to call .status:
if (response.status == 'unknown')
About your question concerning the signed_request: it is used to get some information, look at Authentication - Signed Request to see what exactly. You don't need another validation method.

Related

how can I get a facebook Page access token from a users access token using php?

I am trying to get a page access token starting out with just a users access token stored in my database and a page id. So far I have not been using the facebook.php instead just using php's curl_* functions. So far I can send posts to the page (with a hard coded page id) but I want to impersonate the page when doing so.
Can I do this easily without facebook.php, that would be nice as it might save me from feeling like I should rewrite what I've done so far. If not, then how would I get the page access token from the facebook object - remember so far at least I don't store user ids or page ids in my db, just user access tokens and of course my app id and secret.
I've been looking at the example for getting page access tokens but I find it not quite what I need as it gets a user object and in so doing seems to force the user to login to facebook each time, but I stored the user access token to avoid exactly that from happening.
Do I need more permissions than manage_page and publish_stream? I tried adding offline_access but it doesn't seem available anymore (roadmap mentions this).
here is some of my code from my most recent attempt which uses the facebook.php file:
// try using facebook.php
require_once 'src/facebook.php';
// Create our Application instance
$facebook = new Facebook(array(
'appId' => $FB_APP_ID, // $FB_APP_ID hardcoded earlier
'secret' => $FB_APP_SECRET, // $FB_APP_SECRET hardcoded earlier
));
$facebook->setAccessToken($FB_ACCESS_TOKEN );
//got user access token $FB_ACCESS_TOKEN from database
// Get User ID -- why?
$user = $facebook->getUser();
//------ get PAGE access token
$attachment_1 = array(
'access_token' => $FB_ACCESS_TOKEN
);
$result = $facebook->api("/me/accounts", $attachment_1);
foreach($result["data"] as $page) {
if($page["id"] == $page_id) {// $page_id hardcoded earlier
$page_access_token = $page["access_token"];
break;
}
}
echo '<br/>'.__FILE__.' '.__FUNCTION__.' '.__LINE__.' $result= ' ;
var_dump($result); //this prints: array(1) { ["data"]=> array(0) { } }
$facebook->setAccessToken($page_access_token );
// Get User ID, why - re-init with new token maybe?
$user = $facebook->getUser();
//------ write to page wall
try {
$attachment = array(
'access_token' => $page_access_token,
'link' => $postLink,
'message'=> $postMessage
);
$result = $facebook->api('/me/feed','POST', $attachment);
echo '<br/>'.__FILE__.' '.__FUNCTION__.' '.__LINE__.' $result= ' ;
var_dump($result);
} catch(Exception $e) {
echo '<br/>'.__FILE__.' '.__FUNCTION__.' '.__LINE__.' $e= ' ;
var_dump($e); /*this gives : "An active access token must
be used to query information about the
current user." */
}
die;
Thanks
PS: I hardcoded the user id and started calling
$result = $facebook->api("/$user_id/accounts", $attachment_1);
and I still get an empty result.
PPS: The Graph API Explorer does not show my fan pages either even though my account is set as the Manager. My attempts to post work but show as being from my account rather than from the page.
PPPS: made a little progress by adding permissions on the graph explorer page to get an access token that way but that doesn't help as I need to the the access token programmatically. When a user with many fan pages logs in to my site I want to show them the list of their facebook fan pages to choose from. In practice aren't the permissions just granted on the app?
PPPPS: the list of permissions on my app now stands at : email, user_about_me, publish_actions
and
Extended Permissions:
manage_pages, publish_stream, create_note, status_update, share_item
do I need more? when I try now I still fail to get anything from the call to:
$facebook->api("/$user_id/accounts", $attachment_1);
Px5S: DOH!!! I see now that I was neglecting to add the manage_pages permissions to my call for a user access token when my scripts first get one and store it in the DB. But when I reuse that new access token I still get the error : "An active access token must be used to query information about the current user." So, can't such tokens be reused? Aren't they long term? will read more stuff...
Here is my functioning code, still messy but seems to work, note the scopes on the first $dialog_url, and please feel free to mock my code or even suggest improvements :
function doWallPost($postName='',$postMessage='',$postLink='',$postCaption='',$postDescription=''){
global $FB_APP_ID, $FB_APP_SECRET;
$APP_RETURN_URL=((substr($_SERVER['SERVER_PROTOCOL'],0,4)=="HTTP")?"http://":"https://").$_SERVER['HTTP_HOST'].$_SERVER['SCRIPT_NAME'].'?returnurl=1';
$code = $_REQUEST["code"];
$FB_ACCESS_TOKEN = getFaceBookAccessToken( );
$FB_ACCESS_TOKEN_OLD = $FB_ACCESS_TOKEN;
//if no code ot facebook access token get one
if( empty($code) && empty($FB_ACCESS_TOKEN) && $_REQUEST["returnurl"] != '1')
{
// if( $_REQUEST["returnurl"] == '1') die;
$dialog_url = "http://www.facebook.com/dialog/oauth?client_id=".$FB_APP_ID."&redirect_uri=".$APP_RETURN_URL."&scope=publish_stream,manage_pages";
header("Location:$dialog_url");
}
if( empty($FB_ACCESS_TOKEN) ){
if($_REQUEST['error_code'] == '200'){
return null;
}else if (!empty($code)){
$token_url = "https://graph.facebook.com/oauth/access_token?client_id=".$FB_APP_ID."&redirect_uri=".urlencode($APP_RETURN_URL)."&client_secret=".$FB_APP_SECRET."&code=".$code;
$access_token = file_get_contents($token_url);
$param1=explode("&",$access_token);
$param2=explode("=",$param1[0]);
$FB_ACCESS_TOKEN=$param2[1];
}else{
return null;
}
}
if(!empty($FB_ACCESS_TOKEN) && $FB_ACCESS_TOKEN_OLD != $FB_ACCESS_TOKEN) {
setFaceBookAccessToken( $FB_ACCESS_TOKEN);
}
$_SESSION['FB_ACCESS_TOKEN'] = $FB_ACCESS_TOKEN;
$page_name = '';
$page_id = getFaceBookPageId(); //from db
if(empty($page_id ) ) return null;
//in case there are multiple page_ids separated by commas
if(stripos($page_id, ',') !== false ){
$page_ids = explode(',', $page_id) ;// = substr($page_id, 0, stripos($page_id, ','));
}
$result = null;
foreach($page_ids as $page_id){
$page_id = trim($page_id);
if( !empty($FB_ACCESS_TOKEN)){
//get page_id
require_once 'src/facebook.php';
// Create our Application instance (replace this with your appId and secret).
$facebook = new Facebook(array(
'appId' => $FB_APP_ID,
'secret' => $FB_APP_SECRET
));
$facebook->setAccessToken($FB_ACCESS_TOKEN );
//------ get PAGE access token
$page_access_token ='';
$attachment_1 = array(
'access_token' => $FB_ACCESS_TOKEN
);
$result = $facebook->api("/me/accounts", $attachment_1);
if(count($result["data"])==0) {
return null;
}
foreach($result["data"] as $page) {
if($page["id"] == $page_id) {
$page_access_token = $page["access_token"];
break;
}
}
//------ write to page wall
try {
$attachment = array(
'access_token' => $page_access_token,
'link' => $postLink,
'message'=> $postMessage
);
$result = $facebook->api('/me/feed','POST', $attachment);
} catch(Exception $e) {
return null;
}
} //end if( !empty($FB_ACCESS_TOKEN))
}//end foreach
return $result; }
Now, I wonder if I can send the same message to several pages at once ...
Yup, just by looping over the ids, see above, it now supports multiple page ids.
And unless someone wants to contribute to the code - there's lots of ways it can be improved - I'm done.

facebook php sdk automatically logs out user

I am trying to integrate facebook login in my website.
I am facing a weird problem wherein the user keeps getting logged out almost every 10 minutes from my website, although he remains logged in to facebook:
I don't have much clue about the cause of the error, but this might help. I had some error in my javascript code and since during that time, javascript was not being executed, I somehow remained logged in.
For implementing facebook login i am using geloginUrl and getLogoutUrl functions in php, I have added some code to post on friends' wall etc in javascript as well.
That is all the relevant information according to me.
Let me know, if any additional details might be required to answer this question correctly.
This is what my code looks like:
$user = $facebook->getUser();
if ($user) {
try {
$user_profile = $facebook->api('/me');
} catch (FacebookApiException $e) {
$user = 0;
}
}
if ($user) {
setcookie('uid', $user, time()+36000, '/', 'mywebsite.com');
setcookie('uname', $user_profile['name'], time()+36000, '/', 'mywebsite.com');
} else {
setcookie('uid', $user, time()+36000, '/', 'mywebsite.com');
setcookie('uname', $user_profile['name'], time()+36000, '/', 'mywebsite.com');
}
$loginUrlParams = array(
'scope' => 'email,user_activities,friends_activities,friends_interests,user_interests',
'redirect_uri' => 'http://mywebsite.com/getfbToken.php'
);
$loginUrl = "";
$logoutUrl = "";
$logoutUrlParams = array( 'next' => 'http://mywebsite.com/logout.php' );
// getting login url from facebook if user is invalid
if (!$user)
$loginUrl = $facebook->getLoginUrl($loginUrlParams);
else
$logoutUrl = $facebook->getLogoutUrl($logoutUrlParams);

unable to to open redirected page after facebook authentication

i am using facebook connect under codeigniter.after authentication i want to redirect on success method of my controller
here is my controller:
class Welcome extends CI_Controller {
function __construct()
{
parent::__construct();
$this->load->model('Facebook_model');
}
function index()
{
$fb_data = $this->session->userdata('fb_data');
$data = array(
'fb_data' => $fb_data,
);
$this->load->view('welcome', $data);
}
function topsecret()
{
$fb_data = $this->session->userdata('fb_data');
if((!$fb_data['uid']) or (!$fb_data['me']))
{
redirect('welcome');
}
else
{
$data = array(
'fb_data' => $fb_data,
);
$this->load->view('topsecret', $data);
}
}
function success()
{
$this->load->view('welcome_message');
}
}
my model for facebook api access:
class Facebook_model extends CI_Model {
public function __construct()
{
parent::__construct();
$config = array(
'appId' => '261066574000678',
'secret' => ' 79e11f65449988965362f58e9a4aabd7',
'fileUpload' => true, // Indicates if the CURL based # syntax for file uploads is enabled.
);
$this->load->library('Facebook', $config);
$user = $this->facebook->getUser();
// We may or may not have this data based on whether the user is logged in.
//
// If we have a $user id here, it means we know the user is logged into
// Facebook, but we don't know if the access token is valid. An access
// token is invalid if the user logged out of Facebook.
$profile = null;
if($user)
{
try {
// Proceed knowing you have a logged in user who's authenticated.
$profile = $this->facebook->api('/me?fields=id,name,link,email');
} catch (FacebookApiException $e) {
error_log($e);
$user = null;
}
}
$fb_data = array(
'me' => $profile,
'uid' => $user,
'loginUrl' => $this->facebook->getLoginUrl(
array(
'scope' => 'email,user_birthday,publish_stream', // app permissions
'redirect_uri' => 'https://sanjay.localhost.com/index.php/welcome/success' // URL where you want to redirect your users after a successful login
)
),
'logoutUrl' => $this->facebook->getLogoutUrl(),
);
$this->session->set_userdata('fb_data', $fb_data);
}
}
since i am testing this on localhost host,i also edited my host file and changed my localhost hostname to sanjay.localhost.com.redirect happens but not happens..i think may be because of querystring.when redirects happens redirect uri is
=">https://sanjay.localhost.com/index.php/welcome/success?state=ff5712299510defa&code=AQCaD-FAd1shuW#=
i am not understanding how to handle state and code inside of query string.
please help.
Thank you for contacting me on my blog. First of all, Facebook is discontinued the localhost support. Her is the link https://developers.facebook.com/bugs/128794873890320.
I have not developed any app using codeigniter, I use CakePHP but the auth follow should be same.
1. Create a fb_login function in user controller.
2. This function will follow this logic.
a. Use $facebook->getUser() to get user id.
b. Then use $facebook->api('/me') to be sure.
3.If you get FacebookApiException then send user to login with Facebook. If you use official SDK then the current url will be added to redirect url.
4.the Facebook will redirect your user after sign in. so you will get data using $facebook->getUser(). Save this data in session for further use in you app. then redirect user to you control page or any other page. CakePHP has setFlash() function wich show what ever msg set in the control panel in view. I think Codeignator should have some thing like this. If not you can simply set a msg in session and redirect user. Then unset the msg after showing the msg.
Here is full code
$uid = $facebook->getUser();
try {
$user_profile = $facebook->api('/me');
} catch (FacebookApiException $e) {
//echo $e->getMessage();
$uid = null;
}
$loginUrl = $facebook->getLoginUrl(
array(
'scope' => 'publish_stream,offline_access,email'
),''
);
if (!$uid) {
echo "<script type='text/javascript'>top.location.href = '$loginUrl';</script>";
exit;
}
//search using uid on your user table to check if the use is returnign user or new user.
if($new_user ==1)
{
$this->Session->setFlash('please sign up');
//redirect to sign up
}
else
{
$this->Session->setFlash('you are good lad');
//reditect to control panel
}

Facebook Connect works offline, but not online "This webpage has a redirect loop"

Access to my developed website is done using facebook connect.
Everything is OK offline, but not online. I get a navigation error (using Chrome) "This webpage has a redirect loop" :
The webpage at https://www.facebook.com/dialog/oauth?client_id=209633612480053&redirect_uri=http%3A%2F%2Fwww.bluward.com%2Faccess%2Flogin_facebook&state=299262ddf89afbf382452df89c9a2ce8&scope=email%2C+user_birthday%2C+user_about_me%2C+user_location%2C+publish_stream&fbconnect=1#= has resulted in too many redirects. Clearing your cookies for this site or allowing third-party cookies may fix the problem. If not, it is possibly a server configuration issue and not a problem with your computer.
Here is the PHP code (using CodeIgniter framework):
public function login()
{
// Get the FB UID of the currently logged in user
$uid = $fb->getUser();
// If the user has already allowed the application, you'll be able to get his/her FB UID
if($uid) {
try {
$profile = $fb->api(array(
'method' => 'users.getinfo',
'uids' => $uid,
'fields' => 'uid, first_name, last_name, pic_square, pic_big, sex, birthday_date, current_location, email'
));
// Only the first user.
$profile = $profile[0];
} catch (FacebookApiException $e) {
return false;
}
// Do stuff when already logged in.
return $profile;
} else {
// If not, let's redirect to the ALLOW page so we can get access
redirect($this->getLoginUrl(array(
'scope' => 'email, user_birthday, user_about_me, user_location, publish_stream',
'fbconnect' => 1
)));
}
}
And depending on the profile info, I check if the user already exists on database, then log him/her, and if not exists on database, signup him/her.
Update:
When I clear Facebook cookies, the redirect to login page is successful, and I'm able to fill email/password.
Unfortunately, when I click "log in" button, I get the same error message as I displayed above.
It looks like your using and older version? Here is how i would handle issue with php-sdk 3.1.1 "the most current version".
require 'src/facebook.php';
$facebook = new Facebook(array(
'appId' => '135669679827333',
'secret' => 'xxxxxxxxxxxxxxxxxxxxxxxxxxxx'
));
$user = $facebook->getUser();
if ($user) {
try {
// Proceed knowing you have a logged in user who's authenticated.
$user_profile = $facebook->api('/me');
} catch (FacebookApiException $e) {
error_log($e);
$user = null;
}
}
if ($user) {
$logoutUrl = $facebook->getLogoutUrl();
} else {
$params = array(
scope => 'read_stream,publish_stream,publish_actions,read_friendlists',
//redirect_uri => $url
);
$loginUrl = $facebook->getLoginUrl($params);
echo '<script> top.location.href=\''.$loginUrl.'\'</script>';
};

Facebook session in an iframe application breaks ajax calls

I've a facebook application, which conncets to facebook via the PHP-SDK.
$facebook = new Facebook(array(
'appId' => FACEBOOK_APP_ID,
'secret' => FACEBOOK_SECRET_KEY,
));
$user = $facebook->getUser();
if ($user) {
try {
$user_profile = $facebook->api('/me');
} catch (FacebookApiException $e) {
error_log($e);
$user = null;
}
}
Everything is fine, I let the browser open and don't respond to my app. After a while I try to send a form in the app via ajax. It seems that the session is invalid? Facebook will authorize my app again load the ajax url into the browsers address bar and attach the new session param to that url and breaks the app.
Is there anything I could do to pretend facebook to "reload" or pass the ajax/form action to the browser address bar? Before every request is processed I check whether the user is still active or not, that might be the problem?
$user = $facebook->getUser();
if ($user != 0) {
if($this->userProfile == null){
try {
// Proceed knowing you have a logged in user who's authenticated.
$this->userProfile = $facebook->api('/me');
} catch (FacebookApiException $e) {
error_log($e);
$user = null;
}
}
}else{
$this->userProfile = null;
}
if ($this->userProfile != null) {
$filterChain->run();
} else {
$loginUrl = $facebook->getLoginUrl(
array('scope' => 'publish_stream','redirect_uri' => 'REDIRECT_URI'));
}
echo("<script> top.location.href='" . $loginUrl . "'</script>");
Should I use an other approch?
Thanks in advance!
You really shouldn't be processing ajax calls the same way as regular page fetches. Generally if something like an expired session happens within an ajax process you want to send an error code back to the main page and let it handle it at the top level.
I'm guessing that the info you send back from this ajax request gets immediately parsed as HTML? Which means that if you send back a <script>top.location=xxx</script> block, it gets executed and redirects the browser to the new location. Again that's probably not the best way to handle things, but it would still work if the redirect_uri were set appropriately (to the url of the page as a whole). Because the getLoginUrl() is called while within the ajax page, the redirect_uri is set to that url instead, so after the new authorization is completed that's where the browser is sent back to (at the top level now). So while probably not the best overall structure, a quick workaround would be to override the redirect_uri setting when you are within an ajax call, and make it point to the parent page instead of itself.