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.
Archived Comments
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...
Nope, you are right! Fixing now.
Fixed!
Much better. Thanks!
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...
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. :)
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.
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. :)
I'm eyeballs deep in modifying your Google OAuth demo for my purposes :)
I think I have what I need :)