Section 14.5. Creating an Authenticating Component

14.5. Creating an Authenticating Component

One feature of the framework is the ability for each component to accept or reject a connection. The framework then tallies all the components ' decisions and dispatches an onConnectAccept or onConnectReject event accordingly (a client is accepted only if all the components accept its connection).

Although this is a powerful feature, creating a component that takes advantage of it is not trivial.

In this section, we are going to create such a component, using Flash Remoting on the server side to connect to a simple ColdFusion component (CFC), which performs the authentication against a users database.

We will also create a client-side application to show how to use the authenticating component alongside other communication components, such as Chat and PeopleList.

Think of the authenticating component as a simplified version of SimpleConnect that requires the user to type in a password to log in.

14.5.1. The Tester Application: AuthConnectTest

The application we are about to build has two states: a login screen and a chat screen.

The login screen contains only an instance of the authenticating component, which we will call AuthConnect, as seen in Figure 14-4.

Figure 14-4. The simple UI of the AuthConnect component

If the user provides a proper username/password pair, he will be taken to the chat screen, as shown in Figure 14-5.

Figure 14-5. The chat screen of the sample authenticating application

Before diving into the details of how the AuthConnect component is built, let's look at how it is used in the sample application, which we will call AuthConnectTest .

14.5.1.1 The client side of AuthConnectTest

The client-side .fla for the AuthConnectTest application is very simple. It contains only one frame and the following assets on the Stage:


m_authConnect

An instance of the AuthConnect component


m_disconnect_pb

A Button instance


m_people

An instance of the PeopleList component that ships with FlashCom


m_chat

An instance of the Chat component that ships with FlashCom


m_txt

A dynamic text field used for debugging

The only code on the frame is a single directive:

 #include "AuthConnectTest.as" 

The AuthConnectTest.as code is pretty simple, as shown in Example 14-6.

Example 14-6. The AuthConnectTest.as source code included in the client .fla
 var l = new Object(  ); l.connected = function (p_evtObj:Object):Void {   showScreen("chat");   m_txt.text = "CONNECTED!";   for (var i in p_evtObj.userData)     m_txt.text += newline + "  ." + i + ": " + p_evtObj.userData[i];   m_chat.connect(m_authConnect.nc);    // Connect the Chat component   m_people.connect(m_authConnect.nc);  // Connect the PeopleList component }; l.disconnected = function (p_evtObj:Object):Void {   showScreen("login"); }; m_authConnect.addEventListener("connected", l); m_authConnect.addEventListener("disconnected", l); m_authConnect.setUp("localhost", "authConnectTest"); l.click = function (p_evtObj:Object):Void {   showScreen("login");   m_chat.close( );   m_people.close( );   m_authConnect.nc.close( ); }; m_disconnect_pb.addEventListener("click", l); function showScreen (p_screen:String):Void {   m_authConnect._visible = (p_screen == "login");   m_disconnect_pb._visible = m_txt._visible = (p_screen == "chat");   m_chat._visible = m_people._visible = (p_screen == "chat"); } showScreen("login"); 

The preceding code defines a listener object ( l ) and defines some event handlers on it. As we will see, AuthConnect dispatches two events: connected and disconnected .

When the connected( ) handler is called, it:

  • Shows the chat screen (using the showScreen( ) function defined at the end of the code)

  • Fills the m_txt text field with the user data that the AuthConnect component includes in the event object

  • Connects the Chat and the PeopleList components to the main NetConnection using the standard connect( ) method provided by all communication components that ship with FlashCom

Notice how we use m_authConnect.nc to refer to the movie's NetConnection. As we will see, the AuthConnect component provides a getter function for the nc property.

Conversely, the disconnected( ) handler returns the user to the login screen. The AuthConnect component dispatches a disconnected event if there is a network glitch or the application gets reloaded on the server side.

After setting up the two event handlers with m_authConnect , we call its public setUp( ) method. As we will see later, the method takes two parameters: the name of the FlashCom Server to which to connect and the name of the application instance to connect to on that server.

The code also defines a click( ) method on the same multipurpose listener object, so that when users click on the Disconnect button, they are taken back to the login screen. The click( ) handler also calls the standard close( ) method on the Chat and PeopleList components, so that they can run their "cleanup" code. Last, the click( ) handler calls close( ) on the movie's NetConnection directly.

After adding the click( ) event listener to the Disconnect button, we define the showScreen( ) utility function, which shows and hides assets on the Stage depending on the application's state. Calling showScreen("login") starts the application in the login state.

Nothing advanced here. Now let's look at the server-side part of this application.

14.5.1.2 The server side of AuthConnectTest

The main.asc code found in the AuthConnectTest application folder is also relatively simple, as shown in Example 14-7.

Example 14-7. The main.asc file for the AuthConnectTest application
 //   framework.asc   is discussed later in this chapter. load("framework.asc"); //   AuthConnect.asc   is available on the book's web site. load("AuthConnect.asc"); load("components/chat.asc"); load("components/people.asc"); AuthConnect.instances["GateKeeper"].setUp("localhost:8500", "cfdocs.authenticate",    "authenticateUser"); application.onConnect = function (p_client, p_username, p_pwd) {   trace("main.asc onConnect(" + p_username + ", " + p_pwd + ")");   application.acceptConnection(p_client); }; application.onDisconnect = function (p_client) {   trace("main.asc onDisconnect!"); }; 

As seen in the preceding code, the AuthConnectTest application first loads the framework.asc , then the server-side code of the AuthConnect component, AuthConnect.asc . As we will see later, the code creates a singleton instance of the AuthConnect component named "GateKeeper". (For those not familiar with the term , singleton refers to an object that is the only instance of its class. It takes its name from the well-known Singleton design pattern.)

The AuthConnect class performs authentication. Normally communication components are instantiated when a client calls a method on them. But an authentication component must be created in advance of any clients connecting. So we use the static AuthConnect.create( ) method to create a singleton instance:

 AuthConnect.create("GateKeeper");   // Create the singleton 

The AuthConnect class uses an instances arraywhich is a built-in part of the Macromedia component frameworkto access and use the singleton instance. Because the instances array is an associative array, "GateKeeper" must be referred to in quotes:

 AuthConnect.instances["GateKeeper"].   methodName   (  ) 

The code also calls the setUp( ) method on the "GateKeeper" instance, passing it three parameters: the name of the ColdFusion server to which to connect, the name of the CFC to use as a remoting service, and the name of the function contained within the CFC to invoke:

 AuthConnect.instances["GateKeeper"].setUp("localhost:8500", "cfdocs.authenticate",    "authenticateUser"); 

The application also loads the Chat and PeopleList components, chat.asc and people.asc .

The simple onConnect( ) and onDisconnect( ) functions are there just to show that the component doesn't interfere with other logic that you might need to put in the application's main.asc .

Looking at the tester application code gave us a little insight into the interface of the AuthConnect component and how it can be used within a larger application.

It is time now to look into the three parts that make up the component: the client-side script ( AuthConnect.as ), the FlashCom server-side script ( AuthConnect.asc ), and the ColdFusion component used for database authentication ( authenticate.cfc ).

14.5.2. The Client Side of AuthConnect

As you have seen in Figure 14-4, the UI for the AuthConnect component is extremely simple. It contains a username input text field (named m_username_txt ), a password input text field (named m_password_txt ), a Connect button ( m_connect_pb ), and a feedback dynamic text field ( m_feedback_txt ).

The following methods , events, and properties allow application elements to communicate with the AuthConnect component:


setUp( ) method

Tells the component what server and what application it should try to connect to.


connected event

The component dispatches this event after the user specifies a valid username and password.


disconnected event

The component dispatches this event if the connection is closed.


nc property

Points to the NetConnection used by AuthConnect.

The client-side AuthConnect code is a regular ActionScript 2.0 class, augmented with the EventDispatcher class. The code in Example 14-8 belongs in a file named AuthConnect.as .

Example 14-8. The AuthConnect.as client-side class definition
 import mx.controls.Button; import mx.events.EventDispatcher; class AuthConnect extends MovieClip {   // EventDispatcher needs these   var addEventListener:Function;   var removeEventListener:Function;   var dispatchEvent:Function;   var dispatchQueue:Function;   // Assets   private var m_username_txt, m_password_txt, m_feedback_txt:TextField;   private var m_connect_pb:Button;   // Private vars   private var m_nc:NetConnection;   private var m_serverName:String, m_appName:String;   private var m_prefix:String;   function AuthConnect (Void) {     m_prefix = "AuthConnect.GateKeeper.";  // Hardcoded name     EventDispatcher.initialize(this);     attachMovie("Button", "m_connect_pb", 0);     m_connect_pb._x = 199.3;     m_connect_pb._y = 25.4;     m_connect_pb.label = "Connect";     m_connect_pb.setSize(60,22);     m_connect_pb.addEventListener("click", this);     m_nc = new NetConnection( );     m_nc["owner"] = this;     m_nc.onStatus = function (p_info:Object):Void {       trace("nc.onStatus:" + p_info.code);       if (p_info.code == "NetConnection.Connect.Success")         this.owner.getUserData( );       else if (p_info.code == "NetConnection.Connect.Rejected") {         this.owner.m_feedback_txt.text = p_info.application.message;       }       else if (p_info.code == "NetConnection.Connect.Closed") {         this.owner.dispatchEvent({type:"disconnected"});       }     };   }   // nc getter   function get nc ( ):NetConnection {     return m_nc;   }   function setUp (p_serverName:String, p_appName:String) {     m_serverName = p_serverName;     m_appName = p_appName;   }   //   m_connect_pb   Button handler   private function click (p_evt:Object):Void {     m_feedback_txt.text = "";     m_nc.connect("rtmp://" + m_serverName + "/" + m_appName,                  m_username_txt.text, m_password_txt.text);   }   private function getUserData (Void):Void {     var res = new Object( );     res.owner = this;     res.onResult = function (p_val:Object):Void {       this.owner.dispatchEvent({type:"connected", userData:p_val});     };     m_nc.call(m_prefix + "getUserData", res);   } } 

After importing Button and EventDispatcher , we define our class as a subclass of MovieClip (for simplicity; we could just as well have extended UIComponent ). We then define a few self-explanatory variables that we are going to need.

In the constructor, we define the m_prefix property. As we will see later, this component is different than other communication components, in that it is first instantiated on the server side and has a well-defined name, independent of what the client-side developer names the AuthConnect component instance on stage. This name is always "GateKeeper".

The rest of the constructor code sets up the Connect button and the NetConnection. When the Connect button is clicked, the AuthConnect.click( ) method is called. When the connection is accepted, the AuthConnect.getUserData( ) method is called. If the connection is rejected due to a bad username and/or password, the error message is displayed in the m_feedback_txt text field. If the connection is closed, a disconnected event is dispatched.

The next definition is for the getter function for nc . This allows other components to reach within AuthConnect and control the NetConnection directly. As you have seen in the tester application, the Disconnect button uses m_authConnect.nc to disconnect from the server.

Then, we define the setup( ) method, which saves the server name and the application name into local variables, used by the click( ) method to connect to the server.

The last method defined is getUserData( ) , which queries the server side of the AuthConnect component for more information about the user who just logged in. This information is then attached to the connected event that gets dispatched, in the userData field of the event object. Notice how we use m_prefix to route the client-to-server call to the component's server-side instance.

14.5.3. The Server Side of AuthConnect

The server-side code for the AuthConnect component is the trickiest. Consider the following code carefully . It is presented in excerpts with commentary here, but the entire listing is available in a single file ( AuthConnect.asc ) on the book's web site.

As before, we use try/catch to ensure that the component's class will be defined only once. Then, we load netservices.asc , which is required for Flash Remoting on the server side.

We define our AuthConnect class as a subclass of FCComponent and call the init( ) method provided by the framework in its constructor. The constructor also defines an activeUsers associative array, which we use to maintain a list of users who are currently logged in to the application. We also use this array to reject users who are already logged in:

 try {var dummy = AuthConnect;} catch (e) { // #ifndef AuthConnect   load("netservices.asc");   load("components/component.asc");   AuthConnect = function (p_name) {     this.init(p_name);     this.activeUsers = new Object( );   };   AuthConnect.prototype = new FCComponent("AuthConnect", AuthConnect);   AuthConnect.onConnect = function (p_client, p_username, p_pwd) {     AuthConnect.instances["GateKeeper"].checkConnect(p_client, p_username, p_pwd);     // Don't return a value, defer accept or reject to   doAccept( )   and   doReject( )   .   };   // The code is continued in the following excerpts. 

Because of how the component framework is created, we have to define the onConnect( ) function as a static member of the AuthConnect class (that is, it is a class method attached directly to AuthConnect and not an instance method attached to AuthConnect.prototype ). For those not familiar with the term, in this context, a member is simply a method or property associated with a class or instance.

In onConnect( ) , we simply defer the decision to accept or reject the user to the checkConnect( ) function of this class's "GateKeeper" singleton instance.

By not returning either TRue or false , we leave the client in a pending state. Its fate will be decided asynchronously by the authenticating ColdFusion component.

First, we need to make some assumptions in the ColdFusion component used to authenticate clients. We expect the CFC to return an object of the following form:

  • The return object has a code property. A code equal to 0 means a successful connection. Any other code means that the username and password pair is not allowed in the application.

  • If the code is 0, we expect the result object to contain a data object, which contains more data for the user who just authenticated successfully. The only required field for the data object is username .

  • If the code is not equal to 0, we expect the result object to contain a msg property, which is a string that explains the error condition (such as "unknown user", "wrong password", etc.).

Next we define AuthConnect.checkConnect( ) , which uses such a CFC to decide whether to accept or reject the connection:

 AuthConnect.prototype.checkConnect = function (p_client, p_username, p_pwd) {     var res = new Object(  );     res.client = p_client;     res.owner = this;     res[this.remotingFunctionName + "_Result"] = function (p_val) {       if (p_val.code == 0) {         if (this.owner.activeUsers[p_val.data.username]) {           AuthConnect.doReject(this.client, {message:"already logged in"});         }         else {           var cGlobal = this.owner.getClientGlobalStorage(this.client);           // Used by other components like Chat           cGlobal.username = p_val.data.username + " " + p_val.data.lastName;           cGlobal.userData = p_val.data;           this.owner.activeUsers[p_val.data.username] = true;           AuthConnect.doAccept(this.client);         }       }       else {         AuthConnect.doReject(this.client, {message:p_val.msg});       }     };     res[this.remotingFunctionName + "_Status"] = function (p_info) {       if (p_info.message == undefined)         p_info.message = "Authenticating Error";       AuthConnect.doReject(this.client, p_info);     };     var service = this.conn.getService(this.remotingServiceName, res);     service[this.remotingFunctionName](p_username, p_pwd);     // Don't return until the result arrives from the remoting call.   }; 

We create a callback object, res , and define two handlers on it: the this.remotingFunctionName+"_Result " handler catches the successful results from the remoting call, while the this.remotingFunctionName+"_Status " handler is invoked by Flash Remoting if the call fails. We will see that this.remotingFunctionName is defined in the setup( ) method of this component.

In the _Result handler, we check the return object's code property. If the code is 0, the CFC authenticated the user. If the user is already logged into the application, we reject the client's connection (using AuthConnect.doReject( ) , described next). Otherwise, we save the client's user data in the client's global storage (discussed later under "The Framework's Memory Management") and accept the connection (using AuthConnect.doAccept( ) , described shortly).

If the code returned is not 0, we simply reject the connection, passing the error message directly to the client.

If the _Status handler gets called, the CFC has detected a problem. In this case, we are forced to reject the connection. Notice how we pass the error object straight to the client, so that the UI can show some descriptive error to the user.

Last, checkConnect( ) performs the actual Flash Remoting call, passing the username and password as parameters.

Now let's look at the doAccept( ) and doReject( ) methods, which follow the preceding code:

 // Called by the remoting result object   AuthConnect.doAccept = function (p_client) {     gFrameworkFC.application.requestAcceptConnection(p_client, this);   };   AuthConnect.doReject = function (p_client, p_errObj) {     p_client.__ERROBJ__ = p_errObj;     gFrameworkFC.application.requestRejectConnection(p_client, this);   }; 

First of all, notice that doAccept( ) and doReject( ) are defined as static (i.e., class) methods of the AuthConnect class, attached directly to AuthConnect , not AuthConnect.prototype . This is also dictated by the component framework. Simply calling application.acceptConnection( ) or application.rejectConnection( ) from within checkConnection( ) wouldn't have worked; the framework expects static methods to run equivalent code.

That's precisely what doAccept( ) and doReject( ) do: they call either requestAcceptConnection( ) or requestRejectConnection( ) on the framework. This will put an end to the "pending" condition of the client and either accept or reject the connection. After these calls (and assuming no other authenticating component is running at the same time), the NetConnection.onStatus( ) handler is invoked on the client side. The onStatus( ) handler receives an info object with a code property of either "NetConnection.Connect.Success" or "NetConnection.Connect.Rejected".

The remainder of the component's code is pretty simple:

 // This is called when a client disconnects.   AuthConnect.prototype.onDisconnect = function (p_client) {     // Remove from table         var name = this.getClientGlobalStorage(p_client).userData.username;     this.activeUsers[name] = false;     // Need to call this at the end since we are overriding the base   onDisconnect( )   .     this.releaseLocalStorage(p_client);   };   // Called by each client upon successful connect.   AuthConnect.prototype.getUserData = function (p_client) {     return this.getClientGlobalStorage(p_client).userData;   }; 

The onDisconnect( ) method is called automatically by the framework whenever a user disconnects. We clear the user's slot in the activeUsers table and call releaseLocalStorage( ) , just as any well-behaved communication component should do.

The getUserData( ) method is the first one called by the client side, as soon as a user is accepted, to get more information about the user.

With us so far? Let's look at the remaining elements, which complete the AuthConnect.asc file listing.

The last method we define is the setUp( ) method we called in the main.asc . In it, we create a remoting connection to the ColdFusion Flash Remoting gateway of choice, and we save the CFC's name and function name so that we can use them in checkConnect( ) .

The last thing we do before ending the catch directive is create a singleton server-side instance of this class, with the name "GateKeeper". This bypasses the lazy instantiation provided by the framework, and gives us a way to address the component immediately, whether for calling setUp( ) on it or to respond to onConnect( ) invocations.

 AuthConnect.prototype.setUp = function (p_remotingServerName,                      p_remotingServiceName, p_remotingFunctionName) {     this.conn = NetServices.createGatewayConnection("http://" +                    p_remotingServerName + "/flashservices/gateway");     this.remotingServiceName = p_remotingServiceName;     this.remotingFunctionName = p_remotingFunctionName;   };   AuthConnect.create("GateKeeper");  // Create the singleton   trace("AuthConnect loaded successfully."); } // #endif 

14.5.4. A Sample CFC to Use with AuthConnect

The last piece of the puzzle is the ColdFusion component that does the authentication. Creating a full-fledged CFC that queries a database to do authentication is beyond the scope of this chapter, but the following code could be used as a starting point and for testing. See Chapter 12 for ColdFusion code that performs such a database query and authentication:

 <cfcomponent>   <cffunction name="authenticateUser" access="remote" returnType="struct">     <cfargument name="p_username" type="string" default="" required="true">     <cfargument name="p_password" type="string" default="" required="true">     // Do your database query here to validate the user's credential.     // Hardcoding two users for testing.     <cfif #p_username# IS "peldi">       <cfif #p_password# IS "pwd">         <cfset res["code"]=0>         <cfset userData=StructNew( )>         <cfset userData["username"]="Giacomo">         <cfset userData["lastName"]="Guilizzoni">         <cfset res["data"]=userData>       <cfelse>         <cfset res["code"]=-2>         <cfset res["msg"]="Wrong Password">       </cfif>     <cfelseif #p_username# IS "brian">       <cfif #p_password# IS "pwd">         <cfset res["code"]=0>         <cfset userData=StructNew( )>         <cfset userData["username"]="Brian">         <cfset userData["lastName"]="Lesser">         <cfset res["data"]=userData>       <cfelse>         <cfset res["code"]=-2>         <cfset res["msg"]="Wrong Password">       </cfif>     <cfelse>       <cfset res["code"]=-1>       <cfset res["msg"]="Unknown user">     </cfif>     <cfreturn #res#>   </cffunction> </cfcomponent> 

The code is fairly self-explanatory. If the username is "peldi" or "brian" and the password is "pwd", it authenticates the client and returns the data for the user.

We realize that the authentication process described in this section may be a little hard to understand at first. We encourage you to read the rest of the chapter and come back to it if you need to.

As always, the code provided for the AuthConnect component should be considered as nothing more than a starting point. One obvious improvement would be to decouple the remoting call from the AuthConnect.asc , so that you can use remoting, a server-side-only shared object, or any other method for authenticating.



Programming Flash Communication Server
Programming Flash Communication Server
ISBN: 0596005040
EAN: 2147483647
Year: 2003
Pages: 203

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net