Ok, so this is the last revision of my Flex Thanksgiving homework. Thanks to everyone for the tips. My only purpose here was to learn a bit and I've definitely done that - thanks to you guys out there. So - enough kissing up. Let me talk about what I did in this last build.
First I expanded core.cfc to include a few new methods: unsecure and secure. They simply return random strings with the secure method adding a static string in front:
<cffunction name="unsecure" access="remote" returnType="numeric" output="false">
<cfreturn randRange(1,10000)>
</cffunction>
<cffunction name="secure" access="remote" returnType="string" output="false" roles="admin">
<cfreturn "secure function " & randRange(1,10000)>
</cffunction>
You may be wondering why I bothered with the unsecure method. I mean - I've done it before easily enough. My authenticate method wasn't secured. Frankly there was no good reason - I just wanted to be complete. Ignore the roles equals part. I'll be getting to that in a second.
To my main stage (remember I was using a ViewStack and I named my different pages as stages) I added two buttons:
<mx:Button id="unsecureButton" label="Call unsecured method." click="callUnsecure()" />
<mx:Button id="secureButton" label="Call secured method." click="callSecure()" />
These two buttons were what I used to test my methods. Now lets talk security. I started off this series saying I wanted to avoid ColdFusion's roles based security. I am not of fan of it. It is probably the only thing about ColdFusion I don't like. Turns out though that it is very darn handy when working with Flex. So handy that I decided to swallow my pride a little and just use it. How difficult is it to use in Flex? Incredibly difficult. Very complex. In fact, I shouldn't even tell you how to do this. I should charge outrageous consulting fees instead. (Actually more than a few people posted this.)
Basically what you do is set login information into your RemoteObject tag. I moved my username/password values out of the old method and into the global scope. I then used setRemoteCredentials. Here is my new function that is run after authentication:
private function checkAuthResult(event):void {
var result = event.result;
if(result == 'false') {
Alert.show("Authentication failed", "Errors", mx.controls.Alert.OK);
} else {
core.setRemoteCredentials(usernameValue,passwordValue);
mainView.selectedChild = mainStage;
}
}
The only real difference here from the last version is the setRemoteCredentials function. You can think of it like adding a badge to your remoteObject. Now all calls will also pass along authentication information. How do you use that? With the CFLOGIN scope. I added an Application.cfc file to the project and used this onRequestStart:
<cffunction name="onRequestStart" returnType="boolean" output="false">
<cfargument name="thePage" type="string" required="true">
<cflogin>
<cfif isDefined("cflogin.name") and isDefined("cflogin.password")>
<cfif application.core.authenticate(cflogin.name, cflogin.password)>
<cfloginuser name="#cflogin.name#" password="#cflogin.password#" roles="#application.core.getRoles(cflogin.name)#">
</cfif>
</cfif>
</cflogin>
<cfreturn true>
</cffunction>
So for those of you who have not used CFLOGIN, the basic thing to remember is that it can auto detect calls made with authentication data (not just from Flash/Flex) and will give you access to the username and password. I took these values and passed them to the authentication method. If it returns true (which it should), then I grab the roles (hard coded in my CFC as "admin") and run the CFLOGINUSER tag. So remember my secured method above that had roles=admin? It will now work correctly since the CFLOGINUSER tag will end up giving me a role of "admin."
So I said this was the last entry, but earlier today J Fernandes showed me a good way to make my source attribute dynamic based on FlashVars. So I'll do one more build tomorrow demonstrating that.
I'm happy this was so easy in the end. But - I'm not happy that I ended up using CFLOGIN. To me it is almost as bad as Evaluate(). How could I get around it? One idea would be to build a proxy CFC. This CFC would have contain the methods I need to run remotely, and each method would
- have a username and password attribute. These would get passed to an authenticate() method and logic would only continue if the authentication passed
- any other arguments would be passed in as a structure and then expanded as arguments to the "real" CFC behind the scenes
This seems like extra work. But it does get rid of CFLOGIN. It also lets me keep my original CFC a bit slimmer and more focused on business logic while my proxy can worry about security.
For those of you out there doing Flex 2 development - how do you handle security?
p.s. Oops, forgot to link to the new build. You can view it here: http://ray.camdenfamily.com/demos/flexsec4/SimpleRemotingTest.html
Archived Comments
Ray - Here is how I handled this on a recent Flex app.
A user logs in using username and password. I retrieve the userId for the suer based on their username, password and whether or not the account is active. I then return an instance of a user object to Flex.
So, I would have a user ActionScript class to be used in Flex. Inside the method I use to handle the login, I check to see if there is a valid userID (if it doesn't equal zero) and if so, set the values of the ActionScript class to those that are returned.
Right or wrong, I have found its easier for me to follow how information is getting updated and passed around if I keep everything as an object like I am used to in my 'regular' web apps.
I use a similar method to Scott (like he said, right or wrong). Once the 'pseudo role' is set in the Flex (or Flash Form) app, I use those values to determine which controls should be visible/enabled for the given user, thus bypassing the need for the methods themselves to be role based (they simply can't click the button to call it!). I personally find it to be easier to manage, but it obviously isn't the most fool proof technique.
Scott, I have to say I don't quite get it. Are you saying you pass a userID with your calls to secure methods? Doesn't that mean someone could guess an ID? Maybe you can blog a full example? Just for me? Please? Thank you? ;)
Obviously the biggest hole in my method is that 'remote' methods are still exposed - would switching the access to 'public' as suggested in your previous post's comments ensure security, or do you not think my method is a good idea at all?
I think it depends. I mean - I feel confident that the roles= secures it enough. If someone tried to run it via a web service it just wouldn't work. You can also detect a WS request using isSOAPRequest. I may not quite get what you mean though. Do you feel it is NOT secure now with the roles= in the method?
No - i think it's totally secure with roles= in the method. All due respect, it just seems like overkill. If we can ensure the methods can not be called via a WS and not be called by Flex users who are not in the proper "role" than are the extra steps (onRequestStart, cflogin, etc) really necessary? I'm not saying your technique is wrong, just trying to wrap my head around all of this :)
But Todd - if you use roles= in the method, then you MUST use cflogon, hence my user of onRequestStart. You need to have that in order for it to work. I do agree though that if you wanted to block remote folks you should just use access=public instead of isSOAPRequest.
This convesation on access=remote safety as a webservice "backdoor" was something that came up at my job recently. We were stuck in a bind because we use flex 2 for some of our admin panel work, yet most of the client-facing sites we build require flash remoting via the access=remote attribute of our functions. So what I ended up doing was creating a "webservice port" of sorts...Basically its a single access point CFC that listens for a proprietary "request packet" that is just an XML request packet that says what it needs to do and the webservice reads it, calls the internal method necessary, and returns the data in one of many response formats we have available (like JSON or something).
This doesn't seem like much of a convenience on the surface, but each request packet requires knowledge of how to construct a secret hash that makes the request only work for 24 hours after its request.
So thats, how we coped with the situation of secure vs. insecure...and of course we have AS classes for automatically generating the request packets and what not.
Just wanted to give you a glimpse of how we handle it (even if its wrong :P)
Ray -
I'm not sure I follow your logic in your last example. By placing the cflogin tag in onRequestStart in application CFC are you not authenticating the user every time the Flex application calls one of the CFC's methods?
If that is correct, why not just use CFLogin in a separate authentication method (see my comment and demo flex app I referenced on your previous example). You could setRemoteCredentials in your remote object before calling that authenication method and then those values would be available to CFLogin.
Once the user has been authenticated once by CFLogin and their role set I don't see the need to authenticate the same user again. The role stays with that user and the Flex app will be able to call the methods secured by role="admin" for example without having to reauthenticate that user.
Bruce
Bruce: I am only authenticating when the Flex app passes in the right info, ie, when sRC is used (sRC == setRemoteCredentials). I don't see how your idea of using a separate method would be any better or worse. I would either authenticate in onRequestStart, or in a method of the CFC. I'd rather it happen in onReqStart. That way I don't have to call some other method, but the exact method I want.
Basically my way uses something built in to CF (the firing of onReqStart) whereas yours is more work. (I'd say.)
I don't think you are right about not needing to re-authenticate. If session variables were in use then it would make sense, but my Flex app isn't using session variables.
Ray:
In my demo app (see your previous example and my comment) I don't reauthenticate the user after he logs in successfully. After the user has been authenticated, the flex app can call the 'role secured' CFC function and the call is now allowed.
You can right click on the demo to see all the code including the CFC.
Bruce
Bruce:
First a quick side note. In the past when I published source with Flex apps that included CFM/CFC files, it didn't work as the CFM/CFC would try to run when you viewed them. I guess Adobe fixed that?
So - I see your app and I'm curious how it works. By default CFLOGIN will set cookies. So your Flex app makes the FR call and correctly picks up the cookies? Even though though you must have a CFLOGIN executed on EVERY request. The mere fact that you run CFLOGIN sets up security for the rest of the request. If you are already logged in the code inside the CFOGIN block won't rerun, but it has to be encountered in order for it to run. Thats not a Flex issue but how CFLOGIN works in general. I'm truly confused as to how your application is running right. :)
Ok - you are right. I modified my CFC's Authentication method to run cflogin (ie, it is all in one step), and I removed my onReqestStart code, and it runs. I need to investigate more now but I'm truly surprised.
Bruce: I just did a test and in my published source, I can't view CFC files.
scratch that - I think it is a misconfig on my apache server. I can hit foo.txt, but not foo.cfc.txt. Apache doesn't seem to like it even though it should load it like a normal TXT file.
Ray:
I think it my application works because...
The default for cfLoginUser is to use a memory-only cookie to store the user information (see http://livedocs.macromedia..... So once I use cfLoginUser to set the name, password, and role for a user, that information is stored in a memory cookie.
The Flex the RemoteObject setRemoteCredentials sets the username and password. After using setRemoteCredentials just once, the username and password are sent to CF each time we use that RemoteObject to call a CFC function.
CF receives the username and password as part of the RemoteObject call to a CFC function. CF checks for the cookie that matches the unique id created using that username and password. CF then finds the role in that cookie.
Bruce
Bruce, all of that makes sense, and meshes with my knowledge of CFLOGIN, but the one part missing is the running of CFLOGIN. It was my understanding that the tag had to run in the request in order to "turn on" the roles based security.
Well, thanks for sharing this!
I pretty much use the same approach as any CF based app. In fact I've even created a login Flex app seperate from my main Flex app. The application.cfm/cfc just redirects the user to the login app if not authenticated. Once authenticed the login app redirects them to the actual app. I could mod this to load the approproiate flex app also instead of a redirect. I do not use cflogin at all either. Have to remember a remote object call is over http, has headers and all, just like a request to a cfm page.
DK
Whats to stop the user decompiling the swf and getting the credentials out?
The credentials aren't stored in the binary.
ah ok, so your using a form :)