Store
Community Documentation

v3 Knowledgebase

Dealing with the 'Token Not Set' error when trying to include your site in a facebook page.

Dealing with the 'Token Not Set' error when trying to include your site in a
facebook page.

The first time that I tried to set up my website to appear in facebook, I got
this error: "No security token has been set within the posted form. All forms
must contain a security token in order for our site to handle its requests."
Like most phpFox users would do, I searched the forums to see if anyone else
had seen this error and how they handled it. I found several cases but was
unable to find a good solution. I decided that I had to take a shot at
finding the cause and see if I could find a solution.

First, the error:

I assumed that this came from my phpFox site since it came as a response to
facebook's request. If this is true, then there should be a language phrase
that has this message, so I did a language phrase search for part of the
message and found the target pharase: error.csrf_token_set

One of the most useful tools that I have to work with phpFox is a copy of the
code on my laptop and the windows grep program. This allows me to search all
or part of the code for any string that I want. In this case I searched for
'error.csrf_token_set' and I got a single hit. It was the file
/module/log/include/service/session.class.php. Looking at the code in this
file I see:

PHP:
// CSRF
if (Phpfox::getParam('core.csrf_protection_level') != 'low' && 

isset(
$_SERVER['REQUEST_METHOD']) && strtolower($_SERVER['REQUEST_METHOD']) == 

'post')
{
    if (!isset(
$_POST[Phpfox::getTokenName()]['security_token']))
    {
        
$this->_log(Phpfox::getPhrase('error.csrf_token_set'));
    }
... 


This has got to be where the error is coming from, since it is the only place
that phrase is used.

Looking at the first if statement, I see that it is testing for the parameter
"core.csrf_protection_level". I also see that if this setting is 'low' it
will bypass the section of the code that produces the error, so the first test
of my theory will be to set the csrf protection level to low and see if the
problem goes away.

After changing the setting, I try my facebook page again and it comes right
up. Success! We have found the cause.

But now we are facing another possible problem. Low level csrf may not be
good enough. It normally comes set by default to medium and lowering it may
not be a good idea, so what other options do I have?

This code was part of the function verifyToken() and looking above the code I
am fortunate to find a plugin hook: log.service_session___verifyToken_start.
This means that I can insert my own code before the test to maybe affect the
outcome. Now I just have to decide what code to use.

If I go to my server access logs, I can find the instances where facebook does
the request to my site. It looks like this (yours may vary):

74.xxx.xxx.xxx - - [29/Sep/2012:09:35:20 -0400] "
POST /?fb_source=bookmark_apps&ref=bookmarks&count=0&fb_bmpos=3_0
HTTP/1.1" 200 140 "http://apps.facebook.com/codeformyfbtemplate/
?fb_source=bookmark_apps&ref=bookmarks&count=0&fb_bmpos=3_0"
"Mozilla/5.0 (Windows NT 5.1; rv:15.0) Gecko/20100101 Firefox/15.0.1"

Within this request I see a POST with several requests, so I could pick one
like 'ref' and test it to see if it exist and if it contains the string
'bookmarks'. If so, I could take some action based on the result.

The code that I used for this test is:
PHP:
if ($sFBtest Phpfox::getLib('request')->get('ref'))
{
     if(
$sFBtest === 'bookmarks')
     {
          
//Do something here;
     
}


Now I have to determine what I am going to do if the test is successful.

Again, looking at the function between the hook and the language phrase call,
I am really fortunate to find an exit. Where it says:
PHP:
if (defined('PHPFOX_SKIP_POST_PROTECTION'))
{
     return;


I should be able to use this to exit the verifyToken function without
triggering an error by defining 'PHPFOX_SKIP_POST_PROTECTION'. The code now
looks like this:
PHP:
if ($sFBtest Phpfox::getLib('request')->get('ref'))
{
     if(
$sFBtest === 'bookmarks')
     {
          
define('PHPFOX_SKIP_POST_PROTECTION'true);
     }


Now I can stick this into a plugin and I should be able to set the csrf
setting to any level I want and still let facebook's request in. I have
attached a picture of the admincp plugin that I created to do this.

Final result was that I was able to sucessfully get Facebook to show my site
and I still have my csrf setting at medium. That being said, I have to say
that I am not a security expert. Whether creating that exception will open a
security hole, I don't know. But if the POST above is all that facebook
sends, then that is all we have to work with.

ADDENDUM:

After doing a little more research, I found that, although the above plugin will correctly identify the request is coming from facebook in most cases, and allow the connection, It does not quarantee that this is the case. This may result in someone sending a similar parameter in order to use this plugin as a way to defeat the medium or high CSRF setting.

The following code will capture the user's id and the security token from the facebook request, send an inquiry back to facebook asking for that user id which will only succeed if the security token received is valid. If it gets back data containing the same user id as the original request, then it sets the bypass constant and allows the request.

PHP:
$app_id Phpfox::getParam('facebook.facebook_app_id');
$app_secret Phpfox::getParam('facebook.facebook_secret');
$my_url Phpfox::getParam('core.path');

if (
$signed_request Phpfox::getLib('request')->get('signed_request'))
{

  
$Data parse_signed_request($signed_request$app_secret);
  
$sUser $Data['user_id'];

  
$token_url "https://graph.facebook.com/" $sUser "?access_token="  $Data['oauth_token'];

  
$user json_decode(file_get_contents($token_url));
  
$sCheckUser $user->id;

  
$bMatch = ($sUser == $sCheckUser) ? true:false;

     if(
$bMatch == true)
     {
          
define('PHPFOX_SKIP_POST_PROTECTION'true);
     }
}

  function 
parse_signed_request($signed_request$secret
  {
    list(
$encoded_sig$payload) = explode('.'$signed_request2); 

    
// decode the data
    
$sig base64_url_decode($encoded_sig);
    
$data json_decode(base64_url_decode($payload), true);

    if (
strtoupper($data['algorithm']) !== 'HMAC-SHA256'
    {
      
error_log('Unknown algorithm. Expected HMAC-SHA256');
      return 
null;
    }

    
// check sig
    
$expected_sig hash_hmac('sha256'$payload$secret$raw true);
    if (
$sig !== $expected_sig
    {
      
error_log('Bad Signed JSON signature!');
      return 
null;
    }

    return 
$data;
  }

  function 
base64_url_decode($input
  {
    return 
base64_decode(strtr($input'-_''+/'));
  } 


CAUTION:

I originally did this with an admincp plugin. This may not be a good idea for those who are not comfortable using phpMyAdmin. A bit of bad code in the plugin can shut down your site. If it is in a file plugin, you can edit the file, but if it is an admincp type plugin, all you can do is use phpMyAdmin to access the table 'plugin' and set the 'is_active' field to '0'. Then you can open your site and fix the code.

I have included the plugin in a zipped file as an attachment. Just unzip it and place it in the /module/log/include/plugin/ folder. Since every request to your site will go through this addon, I can't gaurantee that it will not slow things down some. I tried to keep this to a minimum by using the first if statement to only process facebook requests.