Timing out users by role in a ColdFusion Application

This post is more than 2 years old.

Late last month a reader asked me if it was possible to override the session timeout so that he could provide different time outs based on a user role. As far as I know there is no direct way of doing this. There may be a way if you get to the underlying Java Session stuff, but I recommended something simpler - if you keep a variable for when the user last hit your site and do a quick time check, you can easily log them out early. To be clear, this is not the same as ending the session, but honestly, thats not what he really needed. He simply needed to toggle a flag (loggedin) from true to false if that time limit had expired. I thought I'd whip up a quick set of example code to demonstrate this.

Two quick notes before I continue. I wrote this using ColdFusion 11. What I'm demonstrating here could easily be done in ColdFusion MX and higher. I'm not going to rewrite it in tags. Ditto for the member functions I used. It should be trivial to port that to ColdFusion 10 as well. Secondly, I didn't use a framework for the two apps I built. I wanted to keep it super simple.

Ok, let's start off with an incredibly simple application that enforces login. First, the Application.cfc.

component {
	
	this.name="diff_session_v1";
	this.sessionManagement="true";
	
	public boolean function onApplicationStart() {
		application.userService = new model.userservice();
		return true;
	}
		
	public boolean function onRequestStart(string req) {
		
		//login attempt
		if(form.keyExists("login") && form.keyExists("username") && form.keyExists("password")) {
			if(application.userService.authenticate(form.username,form.password)) {
				session.isLoggedIn=true;	
			}
		}
		
		if(!session.isloggedin && req.listLast("/") != 'login.cfm') {
			location(url="login.cfm",addToken=false);
		}
		
		if(url.keyExists("init")) {
			applicationStop();
			location(url="./", addToken=false);	
		}
		return true;
	}
	
	public void function onSessionStart() {
		session.isloggedin=false;
	}
	
}

I'm assuming nothing here is new to folks. This is the same authentication logic you have probably used in a hundred or so applications. My userservice.cfc is literally a method that checks if username and password are "admin". I won't bother sharing that (but you can see it in the attachment). My index.cfm simply says "Hello World" and the login.cfm file is a form, nothing more. Again, this is just the bare minimum. Now let's look at the updated version.

component {
	
	this.name="diff_session_v2a";
	this.sessionManagement="true";
	//two minute timeout by default
	this.sessionTimeout = createTimeSpan(0,0,2,0);
	
	public boolean function onApplicationStart() {
		application.userService = new model.userservice();
		return true;
	}
		
	public boolean function onRequestStart(string req) {

		//timeout for non admins
		if(session.isloggedin && session.auth.role == "user" && dateDiff("s",session.lasthit,now()) > 60) {
			session.isLoggedIn=false;
			session.delete("auth");	
		}
		
		//login attempt
		if(form.keyExists("login") && form.keyExists("username") && form.keyExists("password")) {
			var authResult = application.userService.authenticate(form.username,form.password);
			if(authResult.status) {
				session.isLoggedIn=true;	
				session.auth = authResult;
			}
		}
		
		if(!session.isloggedin && req.listLast("/") != 'login.cfm') {
			location(url="login.cfm",addToken=false);
		}
		
		if(url.keyExists("init")) {
			applicationStop();
			location(url="./", addToken=false);	
		}
		return true;
	}
	
	public void function onRequestEnd(string req) {
		if(session.isLoggedIn) session.lasthit = now();
	}
		
	public void function onSessionStart() {
		session.isloggedin=false;
	}
	
}

Let's cover the important changes, one by one.

First, I've specified a timeout for the Application. Technically this isn't required, but it makes it a bit easier to test. The biggest change is in onRequestStart. Whereas before we simply had two checks (one for logging in, one to see if authenticated), we've added a new check to see if the user is logged in, has a role of user, and has been idle for more than 60 seconds. I kinda feel bad about this logic being here, it seems like perhaps it should be in the userService, but, I think you get the point. If we determine that "too much" time has passed (and the value is arbitrary), then we mark the user as logged out.

I do want to share the userService now as it is a tiny bit more complex. It now returns a structure that includes a status and user information as well.

component {

	public struct function authenticate(required string username, required string password) {
		//admin:admin
		if(username == "admin" && password == "admin") {
			return { id:1, role:"admin", status:true};	
		}
		if(username == "user" && password == "user") {
			return { id:2, role:"user", status:true};	
		}
		return { status:false };
	}	
}

Anyway, I hope this is useful. I've included both versions as an attachment to this blog entry.

Download attached file.

Raymond Camden's Picture

About Raymond Camden

Raymond is a senior developer evangelist for Adobe. He focuses on document services, JavaScript, and enterprise cat demos. If you like this article, please consider visiting my Amazon Wishlist or donating via PayPal to show your support. You can even buy me a coffee!

Lafayette, LA https://www.raymondcamden.com

Archived Comments

Comment 1 by Miguel-F posted on 10/8/2014 at 10:45 PM

You might want to check the code snippets that you shared here. They all seem exactly the same to me. Maybe I need more caffeine...

Comment 2 by Raymond Camden posted on 10/8/2014 at 10:46 PM

Nope, you are right! Fixing now.

Comment 3 by Raymond Camden posted on 10/8/2014 at 10:47 PM

Fixed!

Comment 4 by Miguel-F posted on 10/8/2014 at 10:51 PM

Much better. Thanks!

Comment 5 by Brian Lang posted on 10/8/2014 at 11:28 PM

How would you handle sessions for a site (like Facebook or Google) that has a really long session timeout - on the order of days, weeks or even months? It doesn't seem practical to use Coldfusion's sessions for this purpose.

I'm thinking that maybe if you stored an authentication token of some sort in the user's browser, that you would just create a new session based on their token information each time the user returns to your site.

Hopefully I'm making sense...

Comment 6 by Raymond Camden posted on 10/8/2014 at 11:38 PM

You can use FB to authenticate the user, but that's done to IDENTIFY the person. Ie, "Facebook says this is Ray." On your CF side, you still use sessions, and they still timeout.

See my blog articles on OAuth w/ FB. I showed this. :)

Comment 7 by Brian Lang posted on 10/9/2014 at 12:26 AM

I guess what I'm really asking is how do you handle the CF Session timeout when the user is still logged into whichever OAuth service you're using (Google, FB, LinkedIn, Twitter, etc...).
The OAuth service simply tells me who is (allegedly) using the computer. I still have to handle the CF Session timeout, say if they come back a week after their previous visit. The CF session will have long timed out but their OAuth service is still logged in.

Do you simply re-authenticate them against OAuth, and then re-apply permissions based on your app requirements? Seems to me that would be the best course of action but I would like your opinion.

Comment 8 by Raymond Camden posted on 10/9/2014 at 12:29 AM

I think you are overthinking it. :) When the user comes back after their session is dead and the OAuth process fires again, FB will still recognize them, it will still tell me (the server), "Yep, this is Ray!" and I mark the user as logged in on my session.

Basically it is like you are trusting FB to tell you who a person is, like your db query to check form.username and form.password and see if it returns one row. But *you* then set session.loggedin=true after that.

See my demos. :)

Comment 9 by Brian Lang posted on 10/9/2014 at 12:31 AM

I'm eyeballs deep in modifying your Google OAuth demo for my purposes :)

I think I have what I need :)