Timing out users by role in a ColdFusion Application

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 developer advocate for Extend by Auth0. He focuses on serverless and enterprise cat demos. If you like this article, please consider visiting my Amazon Wishlist or donating via PayPal to show your support.

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

Comments