Search code examples
scalacookiesplayframeworkplayframework-2.2

How to set a cookie in ActionBuilder in Play Framework 2.2 / Scala?


To set a cookie you usually manipulate the result in an action like Ok().withCookies(…).

I created an AuthenticatedAction extends ActionBuilder[AuthenticatedRequest] and need to update the expiry date of a user's cookie by setting a new cookie with a new maxAge sometimes. I cannot figure out how to do this, because I can't find a way to manipulate the result.

Within the invokeBlock function I call block(new AuthenticatedRequest(identity, request)), which returns a Future[SimpleResult] and I cannot use withCookies() on a Future[SimpleResult].

Here's my custom AuthenticatedAction:

class AuthenticatedRequest[A](val identity: Identity, request: Request[A]) extends WrappedRequest[A](request)

object AuthenticatedAction extends ActionBuilder[AuthenticatedRequest] {
    def redirectToLogin = {
        Redirect("/login")
    }

    def invokeBlock[A](request: Request[A], block: (AuthenticatedRequest[A]) => Future[SimpleResult]) = {
        request.cookies.get("mycookie").map { cookie =>

            val maybeIdentity = Auth.validateAndTouchTokenAndGetUser(cookie.value)
            maybeIdentity.map { identity =>

                // If it's a persistent session, update timestamp by sending a new cookie sometimes!
                // To simplify this example, assume we always want to set a new cookie.
                val futureResult = block(new MaybeAuthenticatedRequest(maybeIdentity, request))
                // What next?
                val newMaxAge = 1234
                // ???result???.withCookies(Cookie("mycookie", cookie.value, newMaxAge))

            } getOrElse {
                // Respond with redirect to login and delete cookie and a warning message
                Future.successful(
                    redirectToLogin
                    .discardingCookies(DiscardingCookie("mycookie"))
                    .flashing("warning" -> "Your session has expired. Please sign in again.")
                )
            }

        } getOrElse {
            // Respond with redirect to login
            Future.successful(redirectToLogin)
        }
    }
}

Solution

  • Ok is a SimpleResult as well. You cannot set cookies for a Future[SimpleResult] but you can do like this:

    val futureResult: Future[SimpleResult] = ...
    futureResult.map(_.withCookies(Cookie("mycookie", cookie.value, newMaxAge))
    

    Update for Blankman:

    The simpliest case for responding with cookies is like this:

    def myAction = Action { 
      ..... 
      Ok(response).withCookies(Cookie("cookie", cookieValue, maxAge)) 
    }
    

    This way you can compose your more complex Action like this (example from my project):

    def safe(doSafe: Request[AnyContent] => Future[SimpleResult]): Action[AnyContent] = Action.async { implicit request =>
      try {
        doSafe(request).map(_.withCookies(Cookie("mycookie", cookie.value, newMaxAge))
      } catch {
        case e: Exception =>
          e.printStackTrace()
          //custom failure response here
      }
    }
    

    Usage:

    def someAction = safe { implicit request =>
      .... //something that returns a Future[SimpleResult]
    }