Jan Wytze
Jan Wytze

Reputation: 3497

play 2.6 add/read request attributes

I am trying to implement authentication for my API. I have created a filter that will load the current user based on the token.

The loading of the user works(Removed it from the example), but now I want to add the user to the request attributes so I can access it in an action or controller. But I can't get it to work:

package filters

import java.util.UUID
import javax.inject.Inject

import akka.stream.Materializer
import play.api.libs.typedmap.TypedKey
import play.api.mvc.{Filter, RequestHeader, Result}
import service.UserService

import scala.concurrent.{ExecutionContext, Future}

class LoadUserFilter @Inject() (implicit val mat: Materializer, ec: ExecutionContext) extends Filter {

  override def apply(nextFilter: RequestHeader => Future[Result]) (requestHeader: RequestHeader): Future[Result] = {
    val newRequest = requestHeader.addAttr(TypedKey.apply[String]("user"), "test")
    System.out.println(newRequest.attrs(TypedKey.apply[String]("user"))) // attribute not found....
    nextFilter(newRequest)
  }
}

This is the exception I get:

Caused by: java.util.NoSuchElementException: key not found: user

For some reason the attribute is not added... I am using the updated request but it still doesn't work.

How can I fix this?

Upvotes: 0

Views: 1669

Answers (2)

mcku
mcku

Reputation: 1443

If I understand the question correctly, you want to extract user id from the request header. I am not sure if Filter is a good place for that purpose. This can be done in different ways, I have chosen ActionBuilder and ActionTransformers for that. See Action Composition on Play Framework website.

First you need a RedirectingAction. Then I extract token through TokenExtractAction, user Id through UserIdExtractAction, and check auth in PermissionCheckAction.

This way it is composable, performant and reuseable. In your controller, instead of Action, use SecureAction. Then you get all the bonus userId, token, etc for free. And if auth fails, it fails silently and without cluttering your controller. This is based on the example I remember seeing on Play's website. But I had to extend it. Among the alternatives presented there, I found this one to be most useful for authenticating every REST request situations.

Also I am using an actorsystem for authentication. You can easily build one for yourself.

case class RedirectingAction @Inject()(parser: BodyParsers.Default)(implicit val executionContext: ExecutionContext)
  extends ActionBuilder[Request, AnyContent] {

  def invokeBlock[A](request: Request[A], block: (Request[A]) => Future[Result]): Future[Result] = {
    block(request)
  }
}

case class TokenExtractAction @Inject()(parser: BodyParsers.Default)(
  implicit val executionContext: ExecutionContext)
  extends ActionTransformer[Request, TokenExtractRequest] {

  def transform[A](request: Request[A]): Future[TokenExtractRequest[A]] = Future.successful {
    val token = request.headers.get(Conf.REST_AUTH_HEADER)
    new TokenExtractRequest(token, Some(TokenTool.decodeToken(token.getOrElse("invalid_token")).plaintext), request)
  }
}

case class UserIdExtractAction @Inject()(parser: BodyParsers.Default)(implicit val executionContext: ExecutionContext)
  extends ActionTransformer[TokenExtractRequest, UserRequest] {

  def transform[A](request: TokenExtractRequest[A]): Future[UserRequest[A]] = Future.successful {
    new UserRequest(
      userAuthRole = TokenTool.tokenGetRoleFromToken(request.token),
      userId = TokenTool.tokenGetIdFromToken(request.token),
      token = request.token,
      plainTextToken = request.plainTextToken,
      request = request
    )
  }
}

case class PermissionCheckActionIndividual @Inject()(authSystem: AuthSystem)(implicit val executionContext: ExecutionContext)
    extends ActionFilter[UserRequest] {
  implicit val timeout: akka.util.Timeout = 5.seconds

  def filter[A](input: UserRequest[A]): Future[Option[Result]] = {
    val token                = input.headers.get(Conf.REST_AUTH_HEADER).orElse(Some("invalid token"))
    val authorizer: ActorRef = authSystem.authorizer
    // verify sig and check user validity
    val res = authorizer ? CheckUserValidityFromToken(token)

    res.flatMap {
      case true =>
        Future(None)
      case false =>
        Future(Some(Forbidden("Not permitted")))
    }
  }
}


class SecureActionProvider @Inject()(val authSystem: AuthSystem, val authService: AuthService)(implicit ec: ExecutionContext,
  parser: BodyParsers.Default) {

  def SecureAction: ActionBuilder[UserRequest, AnyContent] =
    RedirectingAction(parser) andThen TokenExtractAction(parser) andThen UserIdExtractAction(parser) andThen PermissionCheckActionIndividual(
      authSystem)
}

in your controller:

def someSecureMethod = secureActionProvider.SecureAction.async ... {
   ...
}  

Hope this helps.

Upvotes: 1

joesan
joesan

Reputation: 15385

How about doing something like this:

nextFilter(requestHeader).map { result =>
  result.withHeaders("user" -> "someUser")
}

Upvotes: 0

Related Questions