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.
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.