poornerd

my thoughts on programming and other nerdy stuff

How to implement a Session Timeout in Play Framework 2

| 13 Comments

If you follow the Play Framework 2 guide for implementing authentication: http://www.playframework.com/documentation/2.2.2/JavaGuide4 – you will notice that there is no session timeout in Play Framework 2. It was there in Play Framework 1, but Play Framework 2 follows a different approach.

I you want to implement your own session timeout, then follow the guide for setting up authentication, by extending the Security.Authenticator, and store a timestamp in the session and keep extending it every time a request is made.

Here is how I did it:

public class Secured extends Security.Authenticator {

    public static final String UNAUTHENTICATED = "unauthenticated";

    public static User getLoggedInUser() {
        if (session("userId") == null)
            return null;
        return User.findById(Long.parseLong(session("userId")));
    }

    public static String getLoggedInUsername() {
        if (session("userId") == null)
            return null;
        return User.findById(Long.parseLong(session("userId"))).getUsername();
    }


    @Override
    public String getUsername(Http.Context ctx) {

        // see if user is logged in
        if (session("userId") == null)
            return null;

        // see if the session is expired
        String previousTick = session("userTime");
        if (previousTick != null && !previousTick.equals("")) {
            long previousT = Long.valueOf(previousTick);
            long currentT = new Date().getTime();
            long timeout = Long.valueOf(Play.application().configuration().getString("sessionTimeout")) * 1000 * 60;
            if ((currentT - previousT) > timeout) {
                // session expired
                session().clear();
                return null;
            } 
        }

        // update time in session
        String tickString = Long.toString(new Date().getTime());
        session("userTime", tickString);

        return User.findById(Long.parseLong(session("userId"))).getUsername();
    }
}

Then just add a sessionTimeout=15 (in Minutes) to your conf file.

If you have read this far, you may as well follow me on Twitter:

Author: poornerd

Tech­nol­o­gist, Entre­pre­neur, Vision­ary, Pro­gram­mer :: Grad­u­ated from USC (Uni­ver­sity of South­ern Cal­i­for­nia) with a degree in Com­puter Sci­ence. After 10+ years of free­lance con­sult­ing and pro­gram­ming, he co-founded Site­Force AG eBusi­ness Solu­tions in 1999 in Munich (München), Ger­many.

13 Comments

  1. Really nice one…Thanks..if u have spare time please make a scala version..

  2. Is this timeout for idle time? I believe it’s max timeout for the session itself rather than the idle time. Which means the session will be cleared after that particular time even if the user is active right? Can you suggest me the way to do it for idle time?

    • This is sort of for Idle time, because if you don’t do anything for the duration of the timeout, then your next request will determine that the session has expired and not allow any secured actions to be executed until the user has been authenticated again. What you are describing is the Play Framework 2’s builtin timeout, that is fixed, and will expire even if the user is active – this of course is the reason for my implementation!

      I suppose it would also be possible to create a separate session timeout cookie, that actually expires… but I think this way is sufficient.

      If you were creating a scaleable architecture, you would have a distributed cache in use (like memcached), so the session would apply every node of the cluster.

  3. I am not a user of Play! so I may miss something but from your code I see that you clear the session when a request to #getUsername() is made and the duration of the last accessTime is bigger than the timeout.

    But this makes me ask myself: what if the user never makes a new request and thus #getUsername() is not called for this user ? Who will clear the session in this case ?

    • Play Framework stores the Session in a Session cookie, so it is never stored on the server anyways. If the user closes his browser, then the session is also cleared. So technically the session is not timing out, but I am forcing the user to have to log back in after a period of inactivity.

  4. Does Play Framework 2 offer the ability to be hosted from an independent server over than it’s own? Trying to build an application where Play does not do the hosting.
    thanks,

  5. Nice post !
    I just made a method out of all of this, currently using it in my app,
    works wonderfully!
    public boolean isSessionNotExpired(Http.Context ctx){
    Long lastSessionTime;
    try{
    lastSessionTime = Long.valueOf(ctx.session().get(SessionsKey.USER_TIME));
    } catch (NumberFormatException e){
    return false;
    }
    boolean notExpired = lastSessionTime+SESSION_MILLIS_TIMEOUT_VALUE > new Date().getTime();
    if (notExpired)
    session(SessionsKey.USER_TIME, Long.toString(new Date().getTime()));
    return notExpired;
    }

  6. Hi Brian,

    Thanks for this post. How do we make sure the user is always signed in unless he/she explicitly signs out? Basically a Remember Me on this machine kind of feature in Play 2

    • It is a session cookie. So if the user closes the browser he is logged out as well. Does that answer the question? I suppose you could change the cookie to not be a session cookie, but then you can just use Play’s builtin session management if you do not need the timeout.

  7. We use play 2.1.3, when I close the browser the session gets expired after 15 mins.

    If I try to open the application in the browser again before 15 mins, I am able to see the home page already being logged-in.

    When the user closes the browser and after 15 mins when the session gets expired I want to log that as ‘logout’ event in the DB.

    How can I do that ? Is there any callback functions that we can setup in the play server to get called after the idletimeout ?

    Thanks

Leave a Reply

Required fields are marked *.