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:
- Original demo. Right click to view and download the source.
- The latest build of the Zend_Framework
Zend_Amf now includes two functions to help with session support:
- isSession – determines if your server supports sessions
- 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:
- 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(); } } } - 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()); } } } - 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!