10 Replies Latest reply on Oct 31, 2011 2:32 AM by cosmits

    Zend AMF Authentication & Authorization

    hidarikani Level 2

      How do I secure my PHP services created with 'Connect To PHP' wizard?

      The web is full of tutorials on connecting to PHP but I found nothing on securing the services.

       

      The 'Connect to PHP' wizard generates a gateway.php which doesn't do authorization.

      Do I have to replace this endpoint with my own? Why doesn't Adobe have tutorials on this?

      maybe PHP apps are not meant to be safe?

        • 1. Re: Zend AMF Authentication & Authorization
          UbuntuPenguin Level 4

          I think authorization is outside of the scope of Flex development seeing how PHP is a middle-tier tech and Flex is a frontend tech.  On the other hand, you could always use sessions to keep track of user credentials and prevent unauthorized access to services.

          • 2. Re: Zend AMF Authentication & Authorization
            keith0507

            Have you checked out Zend_Acl on

            Wade Arnold's blog, Wade is the developer of Zend_Amf http://wadearnold.com

             

            I also created a series of tutorials at http://www.keithcraigo.com/build-a-better-login-with-flex-zend_amf-zend_auth-and-zend_acl- on-the-php-side

            on this as well.

             

            Hope this helps.

            Keith

            • 3. Re: Zend AMF Authentication & Authorization
              hidarikani Level 2

              Your tutorial is for Flex Builder 3 which lacks data-centric features.

               

              From Zend docs: "Zend_Amf_Server allows you to specify authentication and authorization hooks to control access to the services. It is using the infrastructure provided by Zend_Auth and Zend_Acl components." This meas authentication via AMF headers is supported.

               

              However:

              1. Services generated by Flash builder 4 extend RemoteObjectServiceWrapper which lacks methods: setCredentials() & setRemoteCredentials(). This means generated services can't send credentials via AMF headers.
              2. Can the gateway.php which contains the Zend_Amf_Server code be modified to include Zend_Acl & Zend_Auth (maybe Flash Builder periodically overwrites the file)?
              3. gateway.php can't be moved from the project's debug folder because its URL is hardcoded into the generated services. This makes versioning of the gateway.php difficult (adding to subversion).
              • 4. Re: Zend AMF Authentication & Authorization
                keith0507 Level 1

                Hi hidarikan,

                Yes but even though the examples I gave are for Flex 3 the concept is the same for Flex 4 minus the generated services feature.

                The generated services are just starting points. You are free to modify them.

                 

                In your PHP code you have to write your auth and acl functions.

                 

                I'm referring to the sample generated code, I don't know how your setup is configured.

                 

                1. AS services are just calls to RemoteObjects, you have two files , _Super_SamplePhp and SamplePhp, you modify SamplePhp to include RemoteObject calls to setCredentials &or setRemoteCredentials operations that you create in your php code.

                 

                AS:

                     operation = new mx.rpc.remoting.Operation(null, "setCredentials");
                    operation.resultType = Object;         
                    operations["setCredentials"] = operation;
                   
                    operation = new mx.rpc.remoting.Operation(null, "setRemoteCredentials");
                    operation.resultType = Object;         
                    operations["setRemoteCredentials"] = operation;

                 

                 

                2. gateway.php is just a config file, no I wouldn't put the Auth and ACL functions here.

                Only time you want to modify this file is if your server configuration changes.

                 

                3. You can modify the path, I generally don't move this file. When you do a release build it will be copied along with the other required files to your production location.

                 

                 

                I hope this helps.

                 

                Keith

                1 person found this helpful
                • 5. Re: Zend AMF Authentication & Authorization
                  hidarikani Level 2

                  First of all, thank you for the replies. I've read your tutorials and would like to ask one more question:

                  Do Flex micro-architectures (for example Cairngorm) provide a substitute for service code generated by Fllash builder?

                   

                  Your approach works, however the Zend_Server docs suggest this:

                   

                  $server->setAuth($auth);
                  $server->setAcl($acl);
                  

                   

                  To do this gateway.php has to be modified because it contains Zend_Server initialization code.

                  • 6. Re: Zend AMF Authentication & Authorization
                    dankokozar Level 1

                    I've been struggling with it, and figured it all out - so, perhaps it could help others.

                     

                    The authentication is called on the server only if credentials supplied from the client (via the remote procedure call headers). This snippet illustrates the setup of custom auth (these are the last 6 lines of gateway.php script):

                     

                    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

                    // Handle request

                     

                    $auth = new My_Amf_Auth(); // authentication


                    $server->setAuth($auth);

                     

                    $acl = new Zend_Acl(); // authorization
                    $server->setAcl($acl);

                     

                    echo $server->handle();

                     

                    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

                     

                     

                    Now, your custom auth should extend Zend_Amf_Auth_Abstract. Since I want to authenticate users from a database, I bring the Zend_Auth_Adapter_DbTable to play. But since I cannot extend both Zend_Amf_Auth_Abstract and Zend_Auth_Adapter_DbTable, I use a composition:

                     

                    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

                     

                    <?php

                     

                    require_once ('Zend/Amf/Auth/Abstract.php');

                     

                    /**
                    * AMF auth class by Danko Kozar, dankokozar.com
                    * @author dkozar
                    *
                    */
                    class My_Amf_Auth extends Zend_Amf_Auth_Abstract {
                       
                        function __construct() {
                       
                        }
                       
                        public function authenticate() {
                           
                            $adapter = My_Db_Adapter::getInstance();            
                            $adapter->setIdentity($this->_username);
                            $adapter->setCredential($this->_password);
                           
                            // the adapter call
                            // you can wrap it into try.. catch and process DB connection errors
                            $result = Zend_Auth::getInstance()->authenticate($adapter);
                           
                            return $result;
                        }
                    }

                     

                    ?>

                     

                    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

                     

                     

                    Here's the adapter class:

                     

                    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

                     

                    <?php

                     

                    /**
                    * DB table adapter auth class for AMF by Danko Kozar, dankokozar.com
                    * @author dkozar
                    * Singleton
                    */
                    class My_Db_Adapter extends Zend_Auth_Adapter_DbTable {
                       
                        protected static $_instance = null;
                       
                        /**
                         * private!
                         * @param My_Db_Adapter $adapter
                         */
                        public function __construct(Zend_Db_Adapter_Abstract $adapter = null) {
                           
                            if (!$adapter)
                                $adapter = new Zend_Db_Adapter_Mysqli(
                                    array(
                                        'dbname' => 'test',
                                        'username' => 'root',
                                        'password' => '')
                                );    
                        
                            parent::__construct($adapter);
                           
                            $this
                                ->setTableName('users')
                                ->setIdentityColumn('username')
                                ->setCredentialColumn('password')
                            ;
                           
                            // just for testing
                    //        $this
                    //            ->setIdentity('username')
                    //            ->setCredential('password')
                    //        ;
                           
                        }
                       
                        /**
                         * @return  My_Db_Adapter
                         */
                        public static function getInstance()
                        {
                            if (null === self::$_instance) {
                                self::$_instance = new self();
                            }
                            return self::$_instance;
                        }
                       
                       
                        public function authenticate() {

                     

                            $_authResult = parent::authenticate();
                               
                            // NOTE: The point is that $result->_identity is an OBJECT (of type stdClass), NOT string
                            // with Zend_Auth_Adapter_DbTable it is internally accomplished by calling its getResultRowObject() method
                            // It constructs the stdClass with properties named after table attributes
                       
                    //        $user = new stdClass();
                    //        $user->role = "administrator";
                    //        $user->username = $_authResult->getIdentity();
                           
                            $identity = $this->getResultRowObject();
                           
                            $result = new Zend_Auth_Result($_authResult->getCode(), $identity);
                           
                            return $result;
                        }
                    }

                     

                    ?>

                     

                    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

                     

                     

                    On the Flex side I have an auto-generated class (MyService) which extends another auto-generated class (_Super_MyService).

                     

                    The point is that the outer one is auto-generated only once (initially), and you can modify it, without worrying to be overwritten on service regeneration.

                     

                    There's a protected property _serviceControl (which is of type RemoteObject) which could be tweaked if needed.

                     

                    I'm tweaking it by of setting the endpoint (with string read from a client side config in preInitializeService() method). Plus, I'm adding 2 more methods, which expose setCredentials and setRemoteCredentials methods of _serviceControl, so I can acces it from my code.

                     

                    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

                     

                    package services.myservice
                    {
                        public class MyService extends _Super_MyService
                        {
                            /**
                             * Override super.init() to provide any initialization customization if needed.
                             */
                            protected override function preInitializeService():void
                            {
                                super.preInitializeService();

                     

                                // Initialization customization goes here
                                _serviceControl.endpoint = "http://localhost/myapp/gateway.php";
                            }
                           
                            public function setCredentials(username:String, password:String, charset:String=null):void
                            {
                                _serviceControl.setCredentials(username, password, charset);
                            }
                           
                            public function setRemoteCredentials(username:String, password:String, charset:String=null):void
                            {
                                _serviceControl.setRemoteCredentials(username, password, charset);
                            }                  
                        }
                    }

                     

                    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

                     

                    So, before calling MyService methods, I'm setting the credentials with setCredentials() method and this runs the authentication on the PHP side:

                     

                    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
                    private var service:MyService;
                    ....
                    service = new MyService(); // ServiceLocator.getInstance().getHTTPService("presetLoader");
                    service.setCredentials("user1", "pass1");
                    var token:AsyncToken = service.getData();

                    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

                     

                    The authentication via Zend_Amf_Server is, by the way, OPTIONAL! Meaning, with no credentials supplied, Zend_Amf_Server will NOT RUN IT. Thus you should rely on Zend_Acl (e.g. roles) to so your permissions and security!

                     

                    Finally, here's the MySQL DB table I've been using for authentication:

                     

                    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

                     

                    --
                    -- Table structure for table `users`
                    --

                     

                    CREATE TABLE IF NOT EXISTS `users` (
                      `id` int(11) NOT NULL AUTO_INCREMENT,
                      `username` varchar(50) NOT NULL,
                      `password` varchar(32) DEFAULT NULL,
                      `role` varchar(45) DEFAULT NULL,
                      `firstname` varchar(50) DEFAULT NULL,
                      `lastname` varchar(50) DEFAULT NULL,
                      `email` varchar(255) DEFAULT NULL,
                      PRIMARY KEY (`id`),
                      UNIQUE KEY `username` (`username`),
                      UNIQUE KEY `id_UNIQUE` (`id`)
                    ) ENGINE=InnoDB  DEFAULT CHARSET=utf8 AUTO_INCREMENT=2 ;

                     

                    --
                    -- Dumping data for table `users`
                    --

                     

                    INSERT INTO `users` (`id`, `username`, `password`, `role`, `firstname`, `lastname`, `email`) VALUES
                    (1, 'user1', 'pass1', 'administrator', 'Danko', 'Kozar', NULL);

                     

                    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~


                    Cheers!

                     

                    Danko

                    1 person found this helpful
                    • 7. Re: Zend AMF Authentication & Authorization
                      dankokozar Level 1

                      Speaking of Cairngorm:

                       

                      1) you can put your services (like MyService) into ServiceLocator

                      2) you can call your service from within a delegate:

                       

                      public class MyCairngormDelegate
                      {
                          private var responder: IResponder;
                          private var service:MyService;
                         
                          public function MyService(responder: IResponder)
                          {
                              service = new MyService();
                              service.setCredentials("user1", "pass1"); // <- setting credentials here (you can reference some values in the Model)
                              this.responder = responder;
                          }
                             
                          //----------------------------------------------------------------------------
                         
                          //----------------------------------------------------------------------------
                         
                          public function getData() : void
                          {
                              var token:AsyncToken = service.getData();
                              token.addResponder(this.responder);
                          }
                      }

                      • 8. Re: Zend AMF Authentication & Authorization
                        dankokozar Level 1

                        I forgot the MyService.php class. Here it is:

                         

                        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

                        <?php

                         

                        /**
                        * PHP service class with authorization
                        * by Danko Kozar, dankokozar.com
                        * @author dkozar
                        *
                        */
                        class MyService
                        {
                            /**
                             * from zend docs:
                             * If the ACL object is set, and the class being called defines initAcl() method,
                             * this method will be called with the ACL object as an argument.
                             * This method can create additional ACL rules and return TRUE,
                             * or return FALSE if no access control is required for this class.
                             *
                             * @param Zend_Acl $acl
                             * @return boolean
                             */
                            public function initAcl($acl)
                            {
                                $acl->addRole(new Zend_Acl_Role("administrator"));
                                $acl->addRole(new Zend_Acl_Role("user"));
                               
                                //acl "allow" method takes 3 parameters (role, resource - class name, privileges - it's function name in this class)
                               
                                // administrator
                                $acl->allow('administrator', 'MyService', 'helloWorld');
                                $acl->allow('administrator', 'MyService', 'getData');
                               
                                // user
                                $acl->allow('user', 'MyService', 'helloWorld');
                                $acl->deny('user', 'MyService', 'getData');
                               
                                //returning true to signal that we want to check privileges before accessing methods of this class
                                //in my tests if we don't return anything it will treat it like we will return false so better return true or false
                                //your intentions will be clear
                                return true;
                            }
                               
                            /**
                             * Hello world method
                             */
                            public function helloWorld(){
                                return "Hello world from MyService service";
                            }   
                           
                            /**
                             *
                             * Returns data
                             * @return [int]
                             */
                            function getData()
                            {
                                $arr = array(1, 2, 3);
                                return $arr;
                            }
                        }
                        ?>

                        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

                         

                        Note that the authorization is being built dinamically inside the initAcl method.

                        • 9. Re: Zend AMF Authentication & Authorization
                          hidarikani Level 2

                          The new Flash Builder for PHP allows to choose the location of gateway.php.

                          This means that gateway.php is no longer auto generated during project creation.

                          I haven't tried this in production yet.

                           

                          I prefer creating a method on the PHP side:

                          public function authenticate($username, $password)
                          
                          the method sets a cookie which proves useful when reloading the Flex app: the user remains logged in.

                          • 10. Re: Zend AMF Authentication & Authorization
                            cosmits Level 1

                            Hi hidarikan

                             

                            RemoteObjectServiceWrapper class has this property

                            serviceControl : AbstractService

                            which is a reference to the wrapped RemoteObject instance.

                             

                            AbstractService  has setCredentials & setRemoteCredentials

                             

                            does this answer your original question?

                             

                            cheers

                            Ajar