Play Framework: How to Add a Header to Every Response

2.8k views Asked by At

In the following Controller, Authenticated extracts the token from the request headers and invokes a given action if and only if the token is valid (the code has been simplified for clarity):

object MyController extends Controller {

  def Authenticated(action: Token => EssentialAction) = EssentialAction { requestHeader =>
    val jwt = requestHeader.headers.get(HeaderNames.AUTHORIZATION) match {
      case Some(header) => s"""$AuthScheme (.*)""".r.unapplySeq(header).map(_.head.trim)
      case _ => requestHeader.getQueryString("auth").map(UriEncoding.decodePath(_, SC.US_ASCII.name))
    }

    jwt match {
      case Some(t) if t.isValid =>
        val token: Token = authService.token(t)
        action(token)(requestHeader)
      case _ => Done(Unauthorized.withHeaders(HeaderNames.WWW_AUTHENTICATE -> AuthScheme))
    }
  }

  def getUser(userId: String) = Authenticated { token =>
    Action.async { request =>
      userService.find(userId).map {
        case Some(user) => Ok(Json.obj("user" -> user.asJson)).withHeaders(
            "token" -> authService.renew(token).asJson.toString
          )
        case _ => NotFound
      }
    }
  }
}

The token returned by authService.token(t) is a JWT (JSON Web Token) and it can be used only once... so I need to return a new token after each request. The idea would be to put the new token in the response headers. That said, is there a way to add the token header to every response without having to invoke withHeader in each action?

2

There are 2 answers

0
Soroosh Sarabadani On BEST ANSWER

Simply you can create a Filter and in Global.scala add WithFilters class.

import play.api.mvc._

object Global extends WithFilters(TokenFilter) {
  ...
}

Here is a Filter sample for logging so you could change it easily to satisfy your needs.

val loggingFilter = Filter { (next, rh) =>
  val start = System.currentTimeMillis

  def logTime(result: PlainResult): Result = {
    val time = System.currentTimeMillis - start
    Logger.info(s"${rh.method} ${rh.uri} took ${time}ms and returned ${result.header.status}")
    result.withHeaders("Request-Time" -> time.toString)
  }

  next(rh) match {
    case plain: PlainResult => logTime(plain)
    case async: AsyncResult => async.transform(logTime)
  }
}
0
Kris On

I'd use ActionComposition. In Java it could look like:

public class YourActionComposition extends Action<YourAnnotation> {

  @With(YourActionComposition.class)
  @Target({ ElementType.TYPE, ElementType.METHOD })
  @Retention(RetentionPolicy.RUNTIME)
  public @interface YourAnnotation {
  }

  public F.Promise<Result> call(Http.Context ctx) throws Throwable {
    Promise<Result> call = delegate.call(ctx);
    // Add something to your headers here
    return call;
  }
}