Brad Newman (any relation to Victor?) asks:
I am working with a legacy app at the moment. The original developer sets most identities in the client scope in the application.cfm file. I need to make sure that clients are unable to login from separate locations and make sure that sessions are destroyed when the browser is closed. I am trying to create some tight security for users. Any suggestions about how I should go about this? Maybe I haven't dug around enough.
There are a couple of questions here and I'd like to start off by tackling the simplest. First - you mentioned storing in the client scope. I assume that was a mistake as any client based login would be a permanent login. If I'm not mistaken, well then you definitely need to move to session variables. Let's assume that was just incorrectly worded though. The next easy question then is how to make the session close when the browser closes. This is trivial. You can simply Enable J2EE Sessions in your ColdFusion Administrator:
Once enabled you will get the behavior you want. It bears repeating though that this won't technically end the session, but will rather give the user a new session when he opens his browser again. So with the easy ones out of the way, let's turn our focus to the more difficult question - preventing logins from separate locations...
The only way to determine a user's location (as far as I know) is to make use of cgi.remote_addr. This is the IP address of the request. It may be a proxy but it will most likely be unique for your users. You have a few options of how you could use it but here is one idea. When the user logs in you want to store their IP address. Storing the IP address on login means that the next time they login you can look at this again and compare. Now obviously that by itself isn't enough. You may login once on Monday and again on Friday halfway around the world. So you also need a time based marker as well. However - you can't simply mark the login time. If you login at 12:00PM you may actively use the site for hours. What you need instead is to mark the time for every request. Luckily there is a simple enough way to do this - make use of onRequestEnd. This adds a bit of overhead to your request, but you can also store another really useful piece of information - the page they are accessing as well. (Popular analytics tools like Google Analytics can give you reports on the last page your user's hit so this is a pretty important stat.) By recording the time of their last hit on every request, you can then check that on login. Based on your application's timeout (check your This scope settings in your Application.cfc, or if not there, the default Session timeout in the ColdFusion Administrator) you can simply see if an active session is still in place.
This is not perfect. I may use your site on my laptop, close up and get on my mobile device. You may want to consider simply warning your users. That's what GMail does and it seems to work well. You also want to ensure that if you have a logout process that you remove the lasttimehit value (or add a lastlogout value) so that a user could immediately logout and switch to another device.
So now that I've answered Brad's question - I'm curious to hear if anyone out there is doing anything like this and what their experience has been.
Archived Comments
By the way - I assume the technique above was simple enough where a code sample wasn't necessary. If I'm wrong (wouldn't be the first time), I could whip up a complete example in a few minutes. Let me know!
I like the simple approach you suggest here - and I do not mean that as an insult, I love simple solutions to seemingly difficult problems. However, it should be noted that this will not stop some one from logging in from behind the same firewall - even from their own home.
If you are using J2EE session vars, can't you use that value stored in application scope to solve this problem? Using the J2EE session ID should actually be able to stop a user from logging in to the app from the same machine using a different browser.
This is great Ray.. I think that storing the last page request time is going to be allot more functional than storing a login time and making the user wait to assure they don't still have an active session.
Thanks for the thoughts!
Brad
Scott - first para: Agreed. second para: Dude - I've got no idea what you mean here. What value in the app scope? Oh - or do you mean use Application scope to store the values instead of the db?
Yea...that does nto make much sense...I suck..I know it.
What I meant was, wouldn't the session ID be a better piece of identifying information than IP address? You can have thousands of computers behind a firewall but your app would see them all as the same IP address.
I think a solution that stores user info (like userId), session ID and last login (either in app scope or DB) would be more effective at making sure that a user can only login from one location at a time...maybe.
Hmm. So user X logs in and you record:
X+session.urltoken+lastlogin
Hmmm. The problem I see is if I switch browsers though. (But realistically, what non-Nerd does that?) I think with the users behind a firewall you have one organization. The chance of a bad guy attacking a good guy in one org is probably minimal. As we all know, all the bad guys are Nigerian scammers. ;)
Interesting - let's see what others say.
Yes, it can cause issues if you switch browsers - on the web based interface for my home router I will get a message if I try to login from Firefox while already logged from Chrome. But, as you pointed out how many non-nerds do that? Hell, hoe many non-nerds know that more than one web browser exists?
I am not so sure I agree that the chance of a 'bad guy' attacking a 'good guy' in one org is minimal. Some businesses are cut throat and I can easily see why one person might try to 'attack' another in order to make them look bad or to get ahead.
Routers/firewalls ned to have some way to know what response is coming back from what request so it can forward to the correct IP address behind the firewall. Is it possible there may be something in the header of the request that could be used to uniquely identify the computer, rather than IP address?
For reasons I no longer remember, we use Client Scope for all sorts of variables inclinding logged in status, roles, etc. I'm not saying this is the best way, but this app is about 10 years old and this method has worked well for us over that time and over every major version of CF in between.
How we handle clearing that info is on logout we have a routine that clears the client and cookie variables. But what about when someone closes their browser without clicking Logout? Everything is done within frames and the main frameset has the "onunload" defined which calls the same logout routine. I know... what about people who turn off JavaScript? Well, this is a corporate app where we can dictate things, and a lot of the rest of the app would not work without JavaScript anyways.
So, that is what we did.
I inherited a CMS that used client based logins (they never expired) and allowed for unrestricted HTML (or any language for that matter) into their database. It's amazing what's out there. Just using session variables worked great for me and was an easy switch using application.cfc. The client was upset at first that they now had to login every time, but they got used to it. And now, their system is magically less buggy since I started using Anti-sammy for their HTML inputs.
My method for handling multiple logins from separate machines was:
1. create 2 new structs in the application scope
2. store active sessions with the token and userid in the activesessions scope
3. on each log in check the struct for the same userid but different token
4. If an older session exists, add that userid and token to the killsessions struct
5. When the first session tries to reload a page, the onrequest will check the killsessions struct for its session, if it exists, it kills the session and takes them to the login page with a notification.
2. store active sessions with the token and userid in the activesessions *struct