Secure session management
import play.api.mvc._
class SessionController extends Controller {
def login = Action { implicit request =>
val username = request.body.asFormUrlEncoded.get("username")(0)
val password = request.body.asFormUrlEncoded.get("password")(0)
// Assume we have a method `validateLogin` to validate user credentials
if (validateLogin(username, password)) {
Ok("Welcome!").withSession("username" -> username)
} else {
Unauthorized("Wrong credentials.")
}
}
def logout = Action { implicit request =>
Ok("Bye!").withNewSession
}
}
In the above code, we have a
SessionController
class which handles user login and logout. When a user logs in, the
login
method is called. This method validates the user's credentials and if they are valid, it creates a new session with the user's username.
The
logout
method, when called, does not invalidate the current session. Instead, it just creates a new session. This means the old session with the user's username is still valid and can be used to make requests to the server. This is the insecure session management vulnerability.
In a secure session management system, the session token should be invalidated when the user logs out. This ensures that even if an attacker gets hold of a session token, they cannot use it to impersonate the user after the user has logged out.
import play.api.mvc._
import play.api.libs.typedmap.TypedKey
class SessionController extends Controller {
// Define a key for the last activity timestamp
val LastActivityKey = TypedKey[Long]("lastActivity")
def login = Action { implicit request =>
val username = request.body.asFormUrlEncoded.get("username")(0)
val password = request.body.asFormUrlEncoded.get("password")(0)
// Assume we have a method `validateLogin` to validate user credentials
if (validateLogin(username, password)) {
// Set the last activity timestamp to the current time
Ok("Welcome!").withSession("username" -> username).withAttrs(Attrs(LastActivityKey -> System.currentTimeMillis))
} else {
Unauthorized("Wrong credentials.")
}
}
def logout = Action { implicit request =>
// Invalidate the session on logout
Ok("Bye!").withNewSession
}
// Add a new action to check the session timeout
def checkTimeout = Action { implicit request =>
request.attrs.get(LastActivityKey) match {
case Some(lastActivity) =>
if (System.currentTimeMillis - lastActivity > 30 * 60 * 1000) { // 30 minutes timeout
// If the session has timed out, invalidate it
Ok("Session timed out.").withNewSession
} else {
// If the session has not timed out, update the last activity timestamp
Ok("Session active.").withAttrs(Attrs(LastActivityKey -> System.currentTimeMillis))
}
case None =>
Unauthorized("No session.")
}
}
}
The updated code includes a mechanism to handle session timeouts. This is done by storing a timestamp of the last activity in the session attributes. Each time a request is made, the
checkTimeout
action is called to check if the session has timed out. If the session has been inactive for more than 30 minutes, it is invalidated. Otherwise, the last activity timestamp is updated.
The
logout
action now invalidates the session, ensuring that the session token cannot be used after the user has logged out.
These changes help to secure the session management by preventing session tokens from being used indefinitely after they have been issued. This reduces the risk of session tokens being used maliciously if they are intercepted or otherwise obtained by an attacker.