For my app I'm implementing the same security as shown in the zentask.
public class Secured extends Authenticator {
@Override
public String getUsername(Context ctx) {
return ctx.session().get("email");
}
@Override
public Result onUnauthorized(Context ctx) {
ctx.flash().put("error", "please login to proceed");
return redirect(routes.Application.index());
}
}
When a user is authenticated isuser session().put("email", email)
;
I have two problems. First: how you invalidate a session when user leaves the app without using the logout? Second more serious one is that I examined the cookie using firefox plugin cookies manager+
and I can copy a cookie and later paste it thus I can access methods without having to first login, basically I can steal sessions
Play Framework uses stateless sessions. There is no state stored on the server side, rather, all the state is stored in the session cookie. To validate a session, Play signs the sessions using a secret key, and validates the signature when a request with a session cookie arrives. If a user was to tamper with the session data, for example, if they changed the email address in the session to someone else's email address, then the signature would not match, and so Play would reject the session cookie.
Yes, you can copy the cookie and use it later. But you can't change the cookie. This means the only cookie that you can "steal" is your own, but stealing from yourself is not really stealing. So no, you can't steal sessions, except through exploiting other vulnerabilities such as XSS, but session tokens are vulnerable to this too.
By default, Play is configured to create "session" cookies, ie, cookies that expire when you close the browser. So if a user quits out of their browser, the browser will delete all the session cookies, and the user will not be logged in anymore. This is the same for session tokens.
There is one consideration to be aware of, and that is that session tokens also expire on the server because the server holds state. Stateless signed sessions, such as those used in Play, don't. However, you can implement an expiration mechanism yourself, by storing a timestamp inside the session when it is created, and verifying that that timestamp is no older than a configured expiration period in the getUsername() method. Since the timestamp is stored in the session, which is signed, the timestamp can't be tampered with without changing the signature, so this simple mechanism is quite safe. A more advanced solution might be to implement a filter that updated that timestamp each time a request came in, so that expiration could be based on last accessed, rather than when the user logged in.
Your assumption is absolutely right, you cannot invalidate a session on the server following the Zentask example. Although the session cookie is signed with the private key from the configuration file, the same unsigned cookie value produces the signed same cookie. As you already figured out, if someone steals the cookie from a user neither the user nor you (the server) can prevent the thief from "login" into the user's account.
There are basically two options now:
- Store a volatile information in the cookie that you and the user knows. An example for that would be the user's password hash. If the user changes this information the old cookie gets invalidated. But I would not recommend using the password hash as it is a valuable information. The bad thing about this is: If the user does not change this information, the cookie will be valid over a long time.
- Make a server-side session management. For this you have to have something like a database. There you store a randomly generated key for the session, the user and the date when the session will be automatically invalidated. You can also store the IP address to improve the security against cookie stealing. The session key must then be written into the cookie. When the user clicks on the logout button you invalidate the current session (or alternatively all session for this user).
Putting simply the user ID in a cookie is not security at all. As you point out, anyone could invent a cookie value.
Sessions: Instead you need to put an arbitrary (e.g. random) value in the cookie, and then on the server look up the identity of the user in a mapping table. That arbitrary value has to change frequently, so you typically have a login session lasting, say, 30 minutes. Each login provides a new arbitrary value, and that value is known as the session ID.
Invalidation: Sessions are invalidated by removing that entry from the lookup table (on the server side) after a period of time without any requests (e.g. 30 minutes). Any request with a session id that is not in the table, is treated like an unauthenticated request, and you prompt for login again. It does not matter if the user forgets to log out.
Hacking: Because the value is arbitrary, there is no way for a hacker to know in advance what the future session id will be. You are still vulnerable to session stealing, but it is much harder: the hacker has to find a session ID only at the time it is being used, and then can use it only for a certain time. You can take some steps to prevent this, such as only allowing requests for a particular session to come from a particular IP address. You can also cycle session IDs quickly, even every request, but there are negative sides of that. In general, providing a unique session ID for every log in, particularly when this is done over HTTPS, is good enough for most authentication needs.
Persistence: If the number of concurrent users is small over any given session period (e.g. 30 minutes) then you don't necessarily need to put this in a database. Maintaining this in memory is low overhead, but has the disadvantage that if you cycle the server, all the users need to log in again. If you do put the session ID in a database, you need to be sure that when the server starts up it can clean out all the old sessions.
User Info: There still is a value to putting the user's email address in a cookie, but only for use as the "default" user id to log in as. This should be treated only as a convenience to the user, and not as an indication that the user is authenticated.