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

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!

This entry was posted in Flex, PHP, Zend and tagged , , , . Bookmark the permalink.

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

  1. Lasse Moos says:

    Great tutorial, however, you oughta get a better code plugin for wordpress ;-)

    I have a question though, how would you go about authenticating other class calls?

    Say I made a class like BackendManager, and inside that had function like:

    public function getProducts()
    {
    }

    How would you make use your class to authenticate the user on each service call, so that an authentication would occur before getProducts was actually invoked?

    /Lasse

    • Keith says:

      Lasse,
      Plugin -yeah I know, I just haven’t had the time to research it.

      I tried to answer this question in your other comment.

  2. Pickle says:

    Thank you for writing this Keith!

    I’ve ported this example to PureMVC, I was wondering if it would be ok if I posted it?

  3. Pickle says:

    The PureMVC port is available here:

    http://www.allflashwebsite.com/article/puremvc-port-of-keith-craigos-flex-zend_amf-zend_auth-zend_acl-zend_session

    Also, I noticed that the flex code does not tell the server to end the session when the client times out. This seems like a slight security risk to me. Are you counting on the server to automatically ending the session when it times out?

    • Keith says:

      Pickle,
      Your absolutely correct. I forgot to set the expiration in my example.
      According to the Zend Framework documentation this can be setup in several ways depending on your configuration.

      Thank you for bringing this to my attention.

      Keith

  4. Holde says:

    Hi Keith,
    I am still working on some error I get. Now it´s this one:

    Fatal error: Uncaught exception ‘Zend_Session_Exception’ with message ‘Session must be started before any output has been sent to the browser; output started in /Applications/MAMP/frameworks/Zend/Loader.php/207′ in /Applications/MAMP/frameworks/Zend/Session.php:419 Stack trace: #0 /Applications/MAMP/htdocs/AccessControlExample/index.php(22): Zend_Session::start() #1 {main} thrown in /Applications/MAMP/frameworks/Zend/Session.php on line 419

    Any idea what could be wrong? I am using MAMP and ZEND 1.8.4

  5. Holde says:

    Okay, I figured it out.

    In the Bootstrap file I use intead of…

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

    I use…

    require_once ‘Zend/Loader/Autoloader.php’;
    $autoloader = Zend_Loader_Autoloader::getInstance();
    $autoloader->suppressNotFoundWarnings(true);
    $autoloader->setFallbackAutoloader(true);

    It´s because of Zend 1.84

    • Keith says:

      Holde,
      Sorry it’s taken me so long to get back to you. I’m glad you were able to figure this out. According to Wade Arnold the Zend Team will be releasing an update today, I just checked and it’s still not addressed. That was for the addDirectory function, not sure if it relates to this though.

      Thanks Keith

  6. kevin says:

    Hi Keith,

    I’m trying do what Lasse is asking about above – but can’t find your response.

    Your updated LoginManager class works well with Holde’s notes above – thanks to both!

    But when I call $auth = Zend_Auth::getInstance()->hasIdentity() from a different class, it’s always false.

    Can you offer any pointers?

    cheers

  7. kevin says:

    Got it working with a small change:

    After reading this (http://framework.zend.com/manual/en/zend.auth.html#zend.auth.introduction.persistence) I changed the following in the verifyUser() function:

    $result = $authAdapter->authenticate();
    to
    $result = Zend_Auth::getInstance()->authenticate($authAdapter);

    Then the identity seems to be persisted and the hasIdentity() method returns true.

    cheers

    • Keith says:

      Hi Kevin,
      Sorry I haven’t responded sooner, I don’t seem to have a lot of time these days. I’m glad you were able to find a solution and I really appreciate you taking the time to share your findings.

      Keith

  8. kevin says:

    No worries mate.

    Thanks again for your examples – I hope you keep them coming! (in time)

  9. bernhard says:

    Hi,
    First of all thanx for these 3 tutorials. Got my Login working with these.
    Now my question:
    Trying to write a programm where users can view different data depending on a partnerid saved in the Access Database.
    I was able to login the user and get the partnerid using $authAdapter->getResultRowObject(array(‘partnerid’));.

    But what i really want is to get this information from another php class on the server.
    I can get the loggedin usernam through
    $auth = Zend_Auth::getInstance();
    $user = $auth->getIdentity();
    but can I also get the other Information saved in the Access Database or do I have to provide the login essentials again and authenticate with Zend_Auth_Adapter_DbTable function to get the other information?

    hope you understand what I want and can help :)

    • Keith says:

      Hi Bernhard,
      I think I understand your question, no you don’t have ask the user to provide their credentials each time, you just have to make sure
      that the user is logged in. Before giving out any information make sure that the session id’s match
      between the browser and what’s stored in the zend_session on the server. Then you can retrieve the db data using the partnerid.

  10. bernhard says:

    Thank you Keith.
    Can you provide an easy way of doing this? I am a real php and flex starter ;)

    • Keith says:

      Hi Bernhard,
      Using my example, You can return the server sessionid by calling , getSessionID(); then compare that return value against the __model.CURRENTSESSIONID that was set when the user successfully logged in.

      // process return
      if( __model.CURRENTSESSIONID != event.result) {
      // user session is invalid, timed out ? They need to login
      // Do any cleanup like remove menu options only available to logged in users

      // reset the menu bar
      __model.MAINNAV.removeAll();

      __model.workflowState = ModelLocator.LOGIN;
      }

      Hope this helps

      Keith

  11. bernhard says:

    another problem – probably because of my bad understanding of php :)

    In the loginmanager.php I set a pid variable in doing $_SESSION['pid']=$pid;

    and I have a getPartnerid() function:
    public function getPartnerId()
    {
    return $_SESSION['pid'];
    }

    from another php class I use this to get the pid for the current session:
    $LM=new LoginManager();
    $partnerId=$LM->getPartnerId();

    Everything works fine for about 2 Minutes. After this I get this error in my php log:
    PHP Notice: Undefined index: pid in /Applications/MAMP/htdocs/lg_verwaltung/phplib/LoginManager.php on line 86

    Can you help me once again Keith :)

  12. SJ says:

    I can’t seem to find a way to download the “original source” a referred to in the doc…?

  13. SJ says:

    Oops, nevermind. I had to navigate to the demo first then right click. My bad.

  14. sohail says:

    Hi ,
    I read all your article it is really great. I run the test application and it is working. But i have a different scenario let me explain. User login in my simple php application. After login a swf(using flex) file showing and perform some operation on button click using zend_amf and set a new session and i cannot access my old session parameters.. Please help me how i can access the session parameters when i was login.
    Thanks in advance.

Leave a Reply

Your email address will not be published. Required fields are marked *

*

*


You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>