Archive

Posts Tagged ‘Flex’

Build a better Login with Adobe Flex, Zend_Amf, Zend_Auth, and Zend_Acl – Sessions

January 10th, 2009 22 comments

Sorry! this tutorial has taken a little longer to write than I had planned.

My employer actually wanted me to work on their projects instead, GEESH! the nerve!

Anyways here is part 3 of this series. In order to follow along you may want to check out the first two articles in this series because this tutorial will build upon the demo presented there.

You can also view and download the completed demo for this tutorial. Just right click to view and download the source.

Build a better Login with Adobe Flex, Zend_Amf, Zend_Auth, and Zend_Acl – On The Flex Side

and

Build a better Login with Adobe Flex, Zend_Amf, Zend_Auth, and Zend_Acl – On The PHP Side

Disclaimer – I don’t claim that this is the best way to do this, there are a lot better coders out there than me, so I encourage you to do some research so that your code implements the best Security measures possible.

Also, I’m returning the sessionID to the client in this tutorial to illustrate a point, in my opinion, unless you absolutely need to do this, the client never even needs to know the sessionID exists. Remember nothing is 100% safe and trust no one with your data not even authenticated users. Most security breaches are from someone on the inside.

Requirements:

Zend_Amf now includes two functions to help with session support:

  1. isSession – determines if your server supports sessions
  2. setSession – defaults the Zend_Session_Namespace to Zend_Amf

For more information please read Wade Arnold’s article – Zend Amf now with php session support as well as the Zend Framework reference guide.

NOTE: If no activity is detected for 10 seconds the viewer is asked to login once more and the sessionid is reset.

On The PHP Side

Bootstrap File

Insert the following just after

$server = new Zend_Amf_Server();

Zend_Session::start();
$server->setSession();
if($server->isSession()) {$server->setSession();}

// Generate a new sessionid for each browser session, helps prevent session hijacking
// This also prevents "PHP Notice:  Undefined index:  PHPSESSID ", from being given in the php log file.
Zend_Session::regenerateId();

Close your bootstrap file.

LoginManager.php

Add

require_once 'Zend/Session.php';

after the last require_once statement.

Add the following functions to the class, we won’t use all of these functions now but they will be there when we need them.
Thanks to Wade Arnold’s article – Zend Amf now with php session support

/** Thanks to Wade Arnold's article - Zend Amf now with php session support **/
	/** increment the current count session variable and return it's value */
    public function getCount()
    {
    	$_SESSION['count']++;
    	return $_SESSION['count'];
    }

    /** return the php session id value */
    public function getSessionID()
    {
    	return session_id();
    }

    /** Tell's php to generate a new session id */
    public function updateSessionID()
    {
    	return session_regenerate_id();
    }

    /** clear the refrence to the count session variable */
    public function unregister() {
    	unset($_SESSION['count']);
    	return true;
     }

Add this function below unregister()

public function generateNewSessionID() {
		$newSIDStatus = $this->updateSessionID();
		if($newSIDStatus===true) {
			return $this->getSessionID();
		}
}

You might be wondering why I’m calling a new function to update the sessionID instead of calling updateSessionID function directly.
updateSessionID returns either “true” orĀ  “false” but later on for the purpose of this tutorial we need the actual sessionID to be returned. Hopefully this makes more sense as we move along.

Move the declaration

// userRoleVO to Privs Map
    $userRolePrivs = new AccessPrivsVO();

to the head of the verifyUser function.

In the verifyUser() switch statement under
case Zend_Auth_Result::SUCCESS:
place the following

//Generate a new sessionID if the session already exists
if (Zend_Session::sessionExists()){
	$this->updateSessionID();
}
$userRolePrivs->sessionID = $this->getSessionID();

just before the break; statement

You can now close the LoginManager.php file

Calling Zend_Session::sessionExists() upon a successful login will ensure that a new sessionid is always generated in case the users client session times out as we will see later.

This will also help prevent session hijacking.

What is Session Hijacking? Simply put, it’s when another person steals your sessionid to impersonate you in order to steal your data.
Wikipedia has more detailed explanation.

On The FLEX Side

AccessControlExample.mxml

we need to add the following import statement

import mx.events.FlexEvent;

Then in the init() function insert

this.systemManager.addEventListener(FlexEvent.IDLE, checkInactivity);

and Add the following in the script block

// If no activity is detected for 10 seconds reset the
// user sessionID in the model.
// Force the user to login in again which will generate a new sessionid.
public function checkInactivity(e:FlexEvent):void {
   // Normally this would be set to a longer time span but for demo purposes 10 seconds is fine
   if(e.currentTarget.mx_internal::idleCounter == 100){
    //reset the sessionid variable in the model
   __model.CURRENTSESSIONID='';
   // reset the menu bar
   __model.MAINNAV.removeAll();
   // we need to add the default option(s) back
    __model.MAINNAV.addItem("Public");
    Alert.show("Sorry your session has timed out, please login to continue.");
    // transfer the user to the login screen
    __model.workflowState =  ModelLocator.LOGIN;
   }
}

You can now close AccessControlExample.mxml

We now need to create 3 additional files:

  1. GenerateSIDEvent.as with the following code:
    package com.business.events
    {
    import com.adobe.cairngorm.control.CairngormEvent;
    
    import flash.events.Event;
    
    public class GenerateSIDEvent extends CairngormEvent
    {
    public static const NEWSID:String = "NewSID";
    
    public function GenerateSIDEvent():void {
    super(NEWSID);
    }
    
    override public function clone():Event {
    return new GenerateSIDEvent();
    }
    
    }
    }
  2. GenerateSIDCommand.as with the following code:
    package com.business.commands
    {
    	import com.adobe.cairngorm.commands.ICommand;
    	import com.adobe.cairngorm.control.CairngormEvent;
    	import com.business.delegates.GetNewSIDDelegate;
    	import com.business.events.GenerateSIDEvent;
    	import com.model.ModelLocator;
    
    	import mx.rpc.IResponder;
    
    	/**
    	 * GenerateSIDCommand
    	 *
    	 *
    	*/
    	public class GenerateSIDCommand implements ICommand, IResponder
    	{
    		public var __model:ModelLocator = ModelLocator.getInstance();
    
    		/**
    		* Constructor
    		*
    		* @param none
    		*
    		*
    		*/
    		public function GenerateSIDCommand()
    		{
    		}
    
    		/**
    		* Calls the delegate to make a request to the server
    		*
    		* @param CairngormEvent event
    		*
    		* @return void
    		*
    		*/
    		public function execute(event:CairngormEvent):void
    		{
    
    			var getNewSIDEvent:GenerateSIDEvent = event as GenerateSIDEvent;
    			var delegate:GetNewSIDDelegate = new GetNewSIDDelegate( this );
    			delegate.getNewSID();
    		}
    
    		/**
    		* Recieves and processes the result from the delegate
    		*
    		* @param Object event
    		*
    		* @return void
    		*
    		*/
    		public function result(event:Object):void {
    			if(event.result) {
    				__model.CURRENTSESSIONID = event.result;
    			}
    		}
    
    		/**
    		* Handles any errors received by the delegate returned from the server
    		*
    		* @param Object event
    		*
    		* @return void
    		*
    		*/
    		public function fault(event:Object):void {
    			trace("[GenerateNewSID] - Error Connecting!" + event.toString());
    		}
    
    	}
    }
  3. GetNewSIDDelegate.as with the following code:
    package com.business.delegates
    {
    	/**
    	 * GetNewSIDDelegate
    	 *
    	 * Makes a request to the server service to re-genearate a users SessionID
    	 *
    	 */
    	public class GetNewSIDDelegate
    	{
    		import mx.rpc.IResponder;
    		import com.adobe.cairngorm.business.ServiceLocator;
    
    		private var responder : IResponder;
    		private var service : Object;
    
    		/**
    		* Initilizes the service call
    		*
    		* @param IResponder responder
    		*
    		* @return nothing
    		*
    		*/
    		public function GetNewSIDDelegate( responder:IResponder ) {
    				this.responder = responder;
    				this.service = ServiceLocator.getInstance().getRemoteObject("LoginService");
    		}
    
    		/**
    		* Command that actually calls the service and adds the responder mapping to the service method call
    		*
    		* @param nothing
    		*
    		* @return void
    		*
    		*/
    		public function getNewSID():void {
    				var call:Object = service.generateNewSessionID();
    				call.addResponder( responder );
    		}
    
    	}
    }

Now we need to modify Controller.as
Add the following event-command mapping

this.addCommand(GenerateSIDEvent.NEWSID,GenerateSIDCommand);

Okay! Almost done, all that’s left is to modify Services.mxml and ControlPanel.mxml.

Add the following to the Services.mxml RemoteObject “LoginService”

<mx:method name="generateNewSessionID"/>

Add the following function to the ControlPanel.mxml script block

public function regenerateSID():void {
  var getNewSIDEvent:GenerateSIDEvent = new GenerateSIDEvent();
  getNewSIDEvent.dispatch();
}

don’t forget to import the GenerateSIDEvent class

import com.business.events.GenerateSIDEvent;

Then in the body add
<mx:Label text=”and sessionid {__model.CURRENTSESSIONID}”/>

after the label for the Current User Role then add
click=”regenerateSID();” to the button with ID=”Super”

That’s it! Now test it out

completed demo for this tutorial

Login with username: Super and password: password
In the control panel click the Super Admin Functions button, this calls the
generateNewSessionID() function in our LoginManager.php which returns a new sessionid.

Make a note of the current sessionid.

Then let the screen timeout after 10 seconds of no activity, so that you are taken back to the login screen, login one more time and now you are given a new sessionid.

Typically you want to regenerate a new session id when performing a significant task such as user login or as in the case of the Super Admin Functions, this could be extra privileges that you want to add extra protection.

Hopefully this helps!

Categories: Flex, PHP, Zend Tags: , , ,

Build a better Login with Adobe Flex, Zend_Amf, Zend_Auth, and Zend_Acl – On The PHP Side

December 26th, 2008 20 comments

This is the server setup procedures for my tutorial Build a better Login with Flex, Zend_Amf, Zend_Auth, and Zend_Acl – On The Flex Side. Consult the first part of this tutorial for project requirements.

We previously created a folder under the MAMP installation called frameworks to serve as the home for the Zend library. Now we need to create a new folder to be the home for our applications server code.

Create a folder in the MAMP/htdocs dir named AccessControlExample.

Create a new folder in AccessControlExample dir and name it phplib, phplib will be the home of our applications main files.

In our main AccessControlExample dir, create a new file called index.php, this is known as the bootstrap file, if you need further clarification about the bootstrap file or any of the Zend Framework, please consult the Zend Framework Reference Guide.

Replace the contents of index.php with the following

// Error Reporting
error_reporting(E_ALL|E_STRICT);

// Turn to off when released to production
ini_set(“display_errors”,”on”);

// Modify include_path to include the
Zend library and the utils dir
ini_set(“include_path”, ini_get(“include_path”)
.PATH_SEPARATOR.”../../frameworks/”.
PATH_SEPARATOR.”../../utils/”);

// Zend Framework Includes
require_once ‘Zend/Loader.php’;
Zend_Loader::registerAutoload();

require_once ‘Zend/Amf/Server.php’;

require_once ‘ConnectionHelper.php’;
require_once ‘phplib/LoginVO.php’;
require_once ‘phplib/LoginManager.php’;

$server = new Zend_Amf_Server();
$server->setClass(‘LoginManager’);
$server->setClass(“LoginVO”);

// Change this to true when released to production
$server->setProduction(false);

//Mapping the ActionScript VO to the PHP VO
//you don’t have to add the package name
$server->setClassMap(“LoginVO”,”LoginVO”);

echo($server->handle());

Basically this file provides a path to the Zend framework and any custom libraries we build that our application will need to function. We also map our PHP value Objects with our Actionscript value Objects here as well. Transferring data as Objects is much more efficient than making multiple calls for every piece of data.

Create a new PHP class inside the MAMP/utils dir named ConnectionHelper.php with the following code

class ConnectionHelper
{
public function __construct() {

$this->host=’localhost’;
$this->dbname=’AccessControlExample’;
$this->username =’root’;
$this->password = ‘root’;

}
}

Modify this file according to your particular needs.

Close ConnectionHelper.php.

Okay, we now need to create the 3 main php class files our application needs.

  1. LoginManager.php – this is the brains of the server side code. This is where we pass our users credentials to in order to authenticate and authorize the user to use our application. Remember Authenticate (Zend_Auth) and Authorize (Zend_Acl) are two different things.
  2. LoginVO.php – this is a value object, a very simple structure that describes our users login attributes.
  3. AccessPrivsVO.php – this is a value object, a very simple structure that describes our users role privileges.

Inside LoginManager.php paste the following,

require_once ‘ConnectionHelper.php’;
require_once ‘LoginVO.php’;
require_once ‘AccessPrivsVO.php’;
require_once ‘Zend/Auth.php’;
require_once ‘Zend/Acl.php’;

/*
* LoginManager
*
* Verify’s the users login credentials and
checks the users role against the ACL for access rights.
*
* @return Access Privileges
*/
class LoginManager {

private $dbAdapter;
private $authAdapter;

/**
* @return mixed
*/
public function __construct() {

// Get a reference to the
singleton instance of Zend_Auth
$this->auth = Zend_Auth::getInstance();

// Create database connection
try {
$conn = new ConnectionHelper();
$this->dbAdapter =
Zend_Db::factory(‘Mysqli’,array(‘host’ => $conn->host,
‘dbname’ => $conn->dbname,
‘username’ => $conn->username,
‘password’ => $conn->password));
}
catch ( Zend_Db_Exception $e){
return “Caught exception: ” . get_class($e) .
“\n Message: ” . $e->getMessage() . “\n”;
}
}

// test

/**
* @return void
*/
public function test() {
return “Success! Test Completed Normally”;
}

/**
*
* Authenticates the user
*
* @todo add routine to verify using SSO
*
* @return mixed
*
*/
public function verifyUser(LoginVO $user) {

$userRole=”;
// Configure the instance
with constructor parameters…
$authAdapter = new Zend_Auth_Adapter_DbTable(
$this->dbAdapter,
‘admin’,
‘username’,
‘password’);

$usr=htmlspecialchars($user->username);
$pwd=htmlspecialchars($user->password);
if($usr == ”){
$authAdapter
->setIdentity(‘guest’)
->setCredential(‘guest’);
}else{
$authAdapter
->setIdentity($usr)
->setCredential($pwd);
}
$result = $authAdapter->authenticate();

switch ($result->getCode()) {

case Zend_Auth_Result::FAILURE_IDENTITY_NOT_FOUND:
$userRole = “guest”;
break;

case Zend_Auth_Result::FAILURE_CREDENTIAL_INVALID:
$userRole = “guest”;
return “FAILURE_CREDENTIAL_INVALID”;
break;

case Zend_Auth_Result::FAILURE:
$userRole = ‘guest’;
break;

case Zend_Auth_Result::FAILURE_IDENTITY_AMBIGUOUS:
$userRole = ‘guest’;
break;

case Zend_Auth_Result::FAILURE_UNCATEGORIZED:
$userRole = ‘guest’;
break;

case Zend_Auth_Result::SUCCESS:

$this->sessionid=$this->getSessionID(); //revised 2/22/2009
// We need to return the authenticated
users role, this will be passed into the Zend_Acl
// getResultRowObject returns a stdClass
object so we need to dereference the role in this manner.
$r=$authAdapter->getResultRowObject(array(‘role’));
$userRole = $r->role;
break;

default:
return “Internal Error! If this problem persist,
please contact your network administrator”;
break;
}

// Set up the ACL (Access Control List)
$acl = new Zend_Acl();
// Add groups to the Role registry using Zend_Acl_Role
// Guest does not inherit access controls.
// Order matters here, we go from the most
restricted to the least restricted
$acl->addRole(new Zend_Acl_Role(‘guest’));
$acl->addRole(new Zend_Acl_Role(‘manager’), ‘guest’);
$acl->addRole(new Zend_Acl_Role(‘admin’), ‘manager’);

// Administrator does not inherit access controls,
All access is granted
$acl->addRole(new Zend_Acl_Role(‘Super’));

// setup the resource privs
$acl->add(new Zend_Acl_Resource(‘viewPublicUI’));
$acl->add(new Zend_Acl_Resource(‘viewRestrictedUI’));
$acl->add(new Zend_Acl_Resource(‘viewLogs’));
$acl->add(new Zend_Acl_Resource(‘createManager’));

// Guest may only view the public interface
$acl->allow(‘guest’, null, ‘viewPublicUI’);

// manager inherits viewPublicUI privilege from guest,
but also needs additional
// privileges
$acl->allow(‘manager’, null, array(‘viewRestrictedUI’));

// admin inherits viewRestrictedUI privilege from
// manager, but also needs additional privileges
$acl->allow(‘admin’, null, array(‘createManager’));

// Super inherits nothing, but is allowed all privileges
$acl->allow(‘Super’);

// userRoleVO to Privs Map
$userRolePrivs = new AccessPrivsVO();
$userRolePrivs->userRole = $userRole;
$userRolePrivs->viewPublicUI =
$acl->isAllowed($userRole, null, ‘viewPublicUI’) ?
“allowed” : “denied”;
$userRolePrivs->viewRestrictedUI =
$acl->isAllowed($userRole, null, ‘viewRestrictedUI’) ?
“allowed” : “denied”;
$userRolePrivs->createManager =
$acl->isAllowed($userRole, null, ‘createManager’) ?
“allowed” : “denied”;
$userRolePrivs->viewLogs =
$acl->isAllowed($userRole, null, ‘viewLogs’) ?
“allowed” : “denied”;

return $userRolePrivs;

}
}

Inside LoginVO.php paste the following,

class LoginVO
{
//public $_explicitType = ‘LoginVO’;
//Thanks Dennis for pointing this out.
public $userName=”;
public $username=”;
public $password=”;
}

The variable $_explicitType is another way you could map the Actionscript VO to the PHP VO.

Inside AccessPrivsVO.php paste the following,

class AccessPrivsVO
{
public $userRole=”;
public $viewPublicUI=”;
public $createManager = ”;
public $viewRestrictedUI = ”;
public $viewLogs = ”;

}

You may have noticed I purposely left off the php closing tag ?>, this is to avoid troublesome errors caused by extra white space.

That’s it. You now have a robust login system that can be modified to meet your needs using Flex, PHP, Zend_Auth and Zend_Acl. Enjoy!

Categories: Flex, PHP, Zend Tags: , ,