Flash Security in Facebook (Using FLEX/AMFPHP)

This is my first tutorial so please bear with me.

A recent personal Facebook project, which included a little flash required me to think about security within my flow. My project revolves around my SWF calling an AMF service that I setup. Of course, like most developers struggling with a new language (for me it’s ActionScript3), security was an afterthought of my main goal: to make a “super awesome Flash app on Facebool, ZOMG!”

According to the Facebook Developer Wiki regarding Flash on Facebook the way to secure your Flash app on Facebook is to do the following:

  • To verify that your Flash object was loaded from a Facebook page, do the following. For security, this technique does not embed your secret key in your Flash app:
  1. Get all the parameters whose names start with fb_sig. (Do not include the fb_sig parameter itself.) In Flex use Application.application.parameters to do this.
  2. Strip the fb_sig_ prefix from all parameters, and make sure the keys are lowercase.
  3. Create a string of the form param1=value1param2=value2param3=value3, etc., sorted by the names (not the values) of the parameters. Note: Do not use ampersands between the parameters.
  4. Separately pass this string and the fb_sig parameter itself to your server, where your secret key is stored.
  5. On your server, append your application secret key to the string that was passed in. The following is returned: param1=value1param2=value2param3=value3myappsecret
  6. On your server, create an MD5 hash of this string.
  7. On your server, compare the generated hash with the fb_sig parameter that was passed in. If they are equal, then your Flash object was loaded by Facebook. (Or by someone who stole your secret key.) In this case respond to the flash object with VALID or a similar code. If the signature is not valid, respond with INVALID.

Now this may seem a little long-winded, which it is, but what’s worse is the real lack of informtion beyond that which is found on the FB Dev Wiki.

So let’s begin with the AMFPHP service before moving to the ActionScript or even the Facebook Developer Application.

Before getting into the PHP code for the AMFPHP service, I HIGHLY reccomend that everyone watch both of the AMFPHP tutorials at Lee Brimelow’s gotoAndLearn() tutorial and blog site.

Direct links to his AMFPHP tutorials:

Introduction to AMFPHP: Part 1
Introduction to AMFPHP: Part 2


Once you have a good feel for creating a AMF service in PHP, look at the code below to see my AMF_Facebook service:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
<?php
/**
 * Facebook variables
 * You do not need to establish a facebook connection at this point. These
 * 		variables are just for hash checking. In fact, you only need the $secret
 * 		but the API key is included here for familiarity.
 */
$api_key = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa';
$secret  = 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb';
 
/* Instantiate the $secretKey var inside of the service (class) */
AMF_Facebook::$secretKey = $secret;
 
/**
 * AMF_Facebook Service
 */
class AMF_Facebook
{
    const EC_BAD_HASH           = 100;
 
    const RESULT_ERROR          = "error";
    const RESULT_SUCCESS        = "success";
 
    private $_result            = null;
    private $_errorArray        = array();
    private $_data              = null;
    public static $secretKey    = null;
 
    /**
     * You do not control the parameters sent here so only do basic or
     *        static methods.
     */
    function __construct()
    {
        // The Constructor!
    }
 
    /**
     * _getResult() is a private service method; it will not be seen by your
     *        AMFPHP service.
     *
     * This method is to assemble the result object to your ActionScript.
     */
    function _getResult()
    {
        switch ($this->_result)
        {
            case self::RESULT_ERROR:
                return array("result"=>$this->_result,"error"=>$this->_errorArray);
                break;
            default:
                return array("result"=>self::RESULT_SUCCESS,"data"=>$this->_data);
                break;
        }
    }
 
    /**
     * getFbMd5($fbSigStr, $fbSig)
     * @param String fb_sig string
     * @param String fb_sig hash
     * @returns Array(validated_hash, fb_sig_hash) (these should be the same thing)
     */
    function getFbMd5($fbSigStr, $fbSig)
    {
        if (md5($fbSigStr.self::$secretKey) != $fbSig)
        {
            $this->_result          = self::RESULT_ERROR;
            $this->_errorArray[]    = array("message"    => "Hash Mismatch",
                                            "code"       => self::EC_BAD_HASH);
            return $this->_getResult();
        }
        $this->_data = array(md5($fbSigStr.self::$secretKey),$fbSig);
        return $this->_getResult();
    }
}
?>

Let’s break this down. First, I created my base class file, named AMF_Facebook.php. I then wrote the following code to begin the AMFPHP service:

/**
 * AMF_Facebook Service
 */
class AMF_Facebook
{
    /**
     * You do not control the parameters sent here so only do basic or
     *        static methods.
     */
    function __construct()
    {
        // The Constructor!
    }
}

I then added some initial error/success handling to the class. You don’t really have to worry about this stuff as it’s just for handling the results of the service and to return the data to your Flash application in a consistent format. (NOTE: I add this to all of my AMFPHP services. Use your own method if you so wish; I’m just trying to save you some time.) Notice the _gerResult() method is preceded by an underscore (”_”), this is to let AMFPHP know that it is a private method and to disallow calling it from outside of the class.

    const EC_BAD_HASH           = 100;
 
    const RESULT_ERROR          = "error";
    const RESULT_SUCCESS        = "success";
 
    private $_result            = null;
    private $_errorArray        = array();
    private $_data              = null;
 
    /**
     * _getResult() is a private service method; it will not be seen by your
     *        AMFPHP service.
     *
     * This method is to assemble the result object to your ActionScript.
     */
    function _getResult()
    {
        switch ($this->_result)
        {
            case self::RESULT_ERROR:
                return array("result"=>$this->_result,"error"=>$this->_errorArray);
                break;
            default:
                return array("result"=>self::RESULT_SUCCESS,"data"=>$this->_data);
                break;
        }
    }

We now need to setup our Facebook variables. In my example I include the variables in the file with the service but you can use a config file with a PHP include or require call. These next two snippets are for setting your Facebook secret key for your service to leverage.
Outside and above the class:

/**
 * Facebook variables
 * You do not need to establish a facebook connection at this point. These
 *         variables are just for hash checking. In fact, you only need the $secret
 *         but the API key is included here for familiarity.
 */
$api_key = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa';
$secret  = 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb';
 
/* Instantiate the $secretKey var inside of the service (class) */
AMF_Facebook::$secretKey = $secret;

Under the private properties declarations within the class:

	public static $secretKey    = null;

Finally the method to do the lions share of the work in this service. This getFbMd5() method is put last but still within the class. What it does is take the value1=data1value2=data2value3=data3 string and concatinates it with the Facebook $secretKey we set earlier and MD5 hashes it. It then compares that hash with the one that is passed from AS3 and returns either an error on a bad hash match or a success.

	/**
     * getFbMd5($fbSigStr, $fbSig)
     * @param String fb_sig string
     * @param String fb_sig hash
     * @returns Array(validated_hash, fb_sig_hash) (these should be the same thing)
     */
    function getFbMd5($fbSigStr, $fbSig)
    {
        if (md5($fbSigStr.self::$secretKey) != $fbSig)
        {
            $this->_result          = self::RESULT_ERROR;
            $this->_errorArray[]    = array("message"    => "Hash Mismatch",
                                            "code"       => self::EC_BAD_HASH);
            return $this->_getResult();
        }
        $this->_data = array(md5($fbSigStr.self::$secretKey),$fbSig);
        return $this->_getResult();
    }

That’s all you need on the AMFPHP side of things.

Next is the ActionScript. I’m coding my AS3 from within the FLEX Builder 3 IDE. Most things are compatible but if you want more info on a purely Flash oriented AS3 environment, again I suggest you watch the AMFPHP tutorials available at gotoAndLearn().

Below is the code for my MXML project:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
    backgroundColor="0x333333"
    verticalAlign="top" layout="absolute"
    creationComplete="initApp()">
 
    <mx:Script>
        <![CDATA[
            import mx.controls.Alert;
 
            public var gw:NetConnection = new NetConnection();
            public var res:Responder;
 
            private function initApp():void
            {
                var fb_sig:Array = new Array();
                var parameters:Object = Application.application.parameters;
 
                for (var s:String in parameters)
                {
                    if (s.slice(0,7) == "fb_sig_")
                    {
                        fb_sig.push(s);
                    }
                }
                fb_sig.sort();
                var hashCheck:String = "";
                for (var i:int = 0; i < fb_sig.length; i++)
                {
                    hashCheck += fb_sig[i].slice(7,fb_sig[i].length)+"="+parameters[fb_sig[i]];
                }
 
                gw.connect("http://facebook.vampyreempire.com/amfphp/gateway.php");
                res = new Responder(onGetMd5Check, onFault);
                gw.call("AMF_Facebook.getFbMd5", res, hashCheck, parameters["fb_sig"]);
            }
 
            private function onGetMd5Check(responds:Object):void
            {
                if (responds.result == "error")
                {
                    this.onFault(responds);
                }
                else
                {
                    // Looks like we got a valid result!
                    Alert.show("This SWF was loaded from Facebook!","Success!");
                }
            }
 
            private function onFault(responds:Object):void
            {
                if (responds.result == "error")
                {
                    Alert.show("Error (#"+responds.error[0].code+"): "+responds.error[0].message,
                               "Error #"+responds.error[0].code);
                }
                else
                {
                    Alert.show("Error: An unknown error has occured","Error");
                }
            }
        ]]>
    </mx:Script>
 
</mx:Application>

So, as you can see, this is just a basic MXML project. From here on, when going over the AS3, I will use AS3 syntax highlighting. When MXML applications first start, you want to use a method, listed in the initial tag as: creationComplete=”initApp()”. This will cause the initApp() method to fire once the Flash application has loaded.

The below method is preceded by a couple variables (gw and res); they are used for the AMFPHP connection. All the “fb_sig” stuff is the array of flashvars that are sent to your application from Facebook. This method builds the value1=data1value2=data2value3=data3 string and then passes it as well s the fb_sig MD5 hash to your AMFPHP service. (NOTE: Be sure to edit the URL that your gw.connect() method is using.)

            public var gw:NetConnection = new NetConnection();
            public var res:Responder;
 
            private function initApp():void
            {
                var fb_sig:Array = new Array();
                var parameters:Object = Application.application.parameters;
 
                for (var s:String in parameters)
                {
                    if (s.slice(0,7) == "fb_sig_")
                    {
                        fb_sig.push(s);
                    }
                }
                fb_sig.sort();
                var hashCheck:String = "";
                for (var i:int = 0; i < fb_sig.length; i++)
                {
                    hashCheck += fb_sig[i].slice(7,fb_sig[i].length)+"="+parameters[fb_sig[i]];
                }
 
                gw.connect("http://facebook.YOURDOMAIN.com/amfphp/gateway.php");
                res = new Responder(onGetMd5Check, onFault);
                gw.call("AMF_Facebook.getFbMd5", res, hashCheck, parameters["fb_sig"]);
            }

The above method requires two new methods to be defined for the responder (res). For now, we will skip over the onFault method but you can see that it handles errors that were set in the AMFPHP service. This method will do one of two things: it will either call the onFault method if the service returned an error (such as a hash mismatch) or it will succeed and alert you to the success.

            private function onGetMd5Check(responds:Object):void
            {
                if (responds.result == "error")
                {
                    this.onFault(responds);
                }
                else
                {
                    // Looks like we got a valid result!
                    Alert.show("This SWF was loaded from Facebook!","Success!");
                }
            }

Again, not a lot of code is needed for this basic security measue.
At this point, I trust you already have a Facebook application created but if not, be sure to have it set as an FBML type of application to use the following example. This is just the basic FBML call to display a Flash app on your Facebook application. (NOTE: Be sure to specify the exact URL in the “swfsrc” attribute.)

<fb:swf swfsrc='http://facebook.YOURDOMAIN.com/link/to/your/swf/AMF_Facebook.swf'
        width='740'
        height='400'
        align='center' />

If all goes well, you should see the following when you load your Facebook application:
FB-AMFPHP-FLEX

Your Name:

Your Email Address:

Your Message:


Tags: , , , , ,

One Comment