The documentation on ActionBuilder contains a pipeline of three nodes: the authentication, adding informations, validating step.
I would like to set session values at the authentication step. I mean the .withSession which comes here Ok(_).withSession(_)
import play.api.mvc._
class UserRequest[A](val username: Option[String], request: Request[A]) extends WrappedRequest[A](request)
class UserAction #Inject()(val parser: BodyParsers.Default)(implicit val executionContext: ExecutionContext)
extends ActionBuilder[UserRequest, AnyContent] with ActionTransformer[Request, UserRequest] {
def transform[A](request: Request[A]) = Future.successful {
new UserRequest(request.session.get("username"), request)
}
}
You'll need to do action composition to add values to the request session like so:
object WithSession extends ActionBuilder[Request] {
def invokeBlock[A](request: Request[A], block: (Request[A]) => Future[Result]) = {
block(request).map(_.withSession("key" -> "value"))
}
}
in your controller:
def index = WithSession {
Ok("result")
}
Related
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?
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.
How about doing something like this:
nextFilter(requestHeader).map { result =>
result.withHeaders("user" -> "someUser")
}
I upgraded my Play application from 2.5 to 2.6 today and i got a problem with ActionBuilder. The docs state:
The Scala ActionBuilder trait has been modified to specify the type of the body as a type parameter, and add an abstract parser member as the default body parsers. You will need to modify your ActionBuilders and pass the body parser directly.
documentation
Sadly I haven't found any example and i don't know how to fix that:
class AuthenticatedRequest[A](val token: ProfileTokenData, request: Request[A]) extends WrappedRequest[A](request)
trait Secured {
object SetExtractor {
def unapplySeq[T](s: Set[T]): Option[Seq[T]] = Some(s.toSeq)
}
def Authenticated = new ActionBuilder[AuthenticatedRequest] with JWTTokenProcess {
override def invokeBlock[A](request: Request[A], block: (AuthenticatedRequest[A]) => Future[Result]): Future[Result] = {
request.jwtSession.claimData.asOpt[JWTToken] match {
case Some(token) => block(new AuthenticatedRequest(ProfileTokenData(null, token.sub, AuthRole.None), request)).map(_.refreshJwtSession(request))
case _ => Future.successful(Unauthorized)
}
}
}
def Registered = new ActionBuilder[AuthenticatedRequest] with JWTTokenProcess {
override def invokeBlock[A](request: Request[A], block: (AuthenticatedRequest[A]) => Future[Result]): Future[Result] =
this.processJWTToken(request, block, Seq(AuthRole.Admin, AuthRole.Customer, AuthRole.Registered))
}
def Customer = new ActionBuilder[AuthenticatedRequest] with JWTTokenProcess {
override def invokeBlock[A](request: Request[A], block: (AuthenticatedRequest[A]) => Future[Result]): Future[Result] =
this.processJWTToken(request, block, Seq(AuthRole.Admin, AuthRole.Customer))
}
def Admin = new ActionBuilder[AuthenticatedRequest] with JWTTokenProcess {
override def invokeBlock[A](request: Request[A], block: (AuthenticatedRequest[A]) => Future[Result]): Future[Result] =
this.processJWTToken(request, block, Seq(AuthRole.Admin))
}
}
Does anyone know whic BodyParser i have to pass as second argument?
Had a similar problem. Play 2.6 injects a ControllerComponents, which has a default body parser. Maybe this helps:
class CheckApiKey(apiKeyToCheck: String, cc: ControllerComponents)
extends ActionBuilder[Request, AnyContent] with ActionFilter[Request] {
...
override protected def executionContext: ExecutionContext = cc.executionContext
override def parser: BodyParser[AnyContent] = cc.parsers.defaultBodyParser
}
I got the same problem. In Play 2.6.x you need to inject BodyParser and extend ActionBuilder. You can also try it this way
class SecuredActionBuilder(val parser: BodyParser[AnyContent])(implicit val executionContext: ExecutionContext) extends ActionBuilder[SecuredRequest, AnyContent] {
def invokeBlock[A](request: Request[A], block: (AuthenticatedRequest[A]) => Future[Result]): Future[Result] = {
request.jwtSession.claimData.asOpt[JWTToken] match {
...
}
}
I am trying to Create customize action for security. I am using Scala Oauth
for handling security in my application and trying to create custom action and wrap the Scala Oauth security in my custom action. According to Play Framework Documentation, i am using two ways for wrapped request object, but unfortunately, i am not getting my custom Request object in custom Action handler. Following are the Ways:
case class AuthRequest[A](user: User, request: Request[A]) extends WrappedRequest[A](request)
First Way
case class CustomSecurityAction[A](action: Action[A]) extends Action[A] with OAuth2Provider{
def apply(request: Request[A]): Future[Result] = {
implicit val executionContext: ExecutionContext = play.api.libs.concurrent.Execution.defaultContext
request.headers.get("Host").map { host =>
authorize(new SecurityDataHandler(host)) { authInfo =>
action(AuthRequest(authInfo.user, request))
}(request, executionContext)
} getOrElse {
Future.successful(Unauthorized("401 No user\n"))
}}
lazy val parser = action.parser
}
object SecurityAction extends ActionBuilder[Request] with OAuth2Provider {
def invokeBlock[A](request: Request[A], block: (Request[A]) => Future[Result]) = {
block(request)
}
override def composeAction[A](action: Action[A]) = new CustomSecurityAction(action)
}
Second Way
object SecurityAction extends ActionBuilder[Request] with OAuth2Provider {
def invokeBlock[A](request: Request[A], block: (Request[A]) => Future[Result]) = {
implicit val executionContext: ExecutionContext = play.api.libs.concurrent.Execution.defaultContext
request.headers.get("Host").map { host =>
authorize(new SecurityDataHandler(host)) { authInfo =>
block(AuthRequest(authInfo.user, request))
}(request, executionContext)
} getOrElse {
Future.successful(Unauthorized("401 No user\n"))
}
}
According to both ways, when i am trying to use user object in my custom handler i am getting following compile time error:
value user is not a member of play.api.mvc.Request[play.api.mvc.AnyContent]
val user = request.user
Following is my handler code:
def testCustomAction = SecurityAction { request =>
val user = request.user
Future.successful(Ok("Apna To Chal Gya"))
}
There is simple problem, in above code. I am using play.api.mvc.Request in invokeBlock method instead of AuthRequest. Please find below code for correction.
object SecurityAction extends ActionBuilder[AuthRequest] {
override def invokeBlock[A](request: Request[A], block: (AuthRequest[A]) => Future[Result]) = {
request match {
case re: AuthRequest[A] => block(re)
case _ => Future.successful(Results.Unauthorized("401 No user\n"))
}
}
override def composeAction[A](action: Action[A]) = CustomSecurityAction(action)
}
since Play Framework 2.4 there is the possibility to use dependency injection (with Guice).
Before I used objects (for example AuthenticationService) in my ActionBuilders:
object AuthenticatedAction extends ActionBuilder[AuthenticatedRequest] {
override def invokeBlock[A](request: Request[A], block: (AuthenticatedRequest[A]) => Future[Result]): Future[Result] = {
...
AuthenticationService.authenticate (...)
...
}
}
Now AuthenticationService is not an object anymore, but a class. How can I still use the AuthenticationService in my ActionBuilder?
Define your action builders inside a trait with the authentication service as an abstract field. Then mix them into your controllers, into which you inject the service. For example:
trait MyActionBuilders {
// the abstract dependency
def authService: AuthenticationService
def AuthenticatedAction = new ActionBuilder[AuthenticatedRequest] {
override def invokeBlock[A](request: Request[A], block(AuthenticatedRequest[A]) => Future[Result]): Future[Result] = {
authService.authenticate(...)
...
}
}
}
and the controller:
#Singleton
class MyController #Inject()(authService: AuthenticationService) extends Controller with MyActionBuilders {
def myAction(...) = AuthenticatedAction { implicit request =>
Ok("authenticated!")
}
}
I didn't like the way one was required to inherit in the above example. But apparently it's possible to simply wrap a object inside class:
class Authentication #Inject()(authService: AuthenticationService) {
object AuthenticatedAction extends ActionBuilder[Request] {
def invokeBlock[A](request: Request[A], block: (Request[A]) => Future[Result]) = {
// Do your thing wit the authService...
block(request)
}
}
}
class YourController #Inject() (val auth: Authentication) extends Controller (
def loggedInUser = auth.AuthenticatedAction(parse.json) { implicit request =>
// ...
}
}
I like accepted answer but for some reason the compiler would not recognize the authService reference. I got around this pretty easily by just sending the service in the method signature, a la...
class Authentication #Inject()(authenticationService: AuthenticationService) extends Controller with ActionBuilders {
def testAuth = AuthenticatedAction(authenticationService).async { implicit request =>
Future.successful(Ok("Authenticated!"))
}
}
My current custom action is this
class UserRequest[A](val user: Option[models.UserProfile],
request: Request[A]) extends WrappedRequest[A](request)
object UserAction extends ActionBuilder[UserRequest] with ActionTransformer[Request, UserRequest] {
def transform[A](request: Request[A]) = Future.successful {
val user = ... get user from session
new UserRequest(user, request)
}
}
Additionally, I want to pass a parameter to UserAction (e.g. role to validate). So in controller I can use it in this way:
def admin = UserAction("admin") { Ok("granted") }
I don't know why you want to do such thing, but you can probably achieve it this way :
class UserRequest[A](val user: Option[models.UserProfile],
request: Request[A]) extends WrappedRequest[A](request)
object UserActionInner extends ActionBuilder[UserRequest] with ActionTransformer[Request, UserRequest] {
def transform[A](request: Request[A]) = Future.successful {
val user = ... get user from session
new UserRequest(user, request)
}
}
object UserAction {
def apply[ A ]( str: String )( block: Request[ A ] => Future[ Result ] ) {
// do something with your str
// Now let UserActionInner do the job
UserActionInner( block )
}
}
// Now you can use it like this.
def admin = UserAction("admin") { Ok("granted") }
Using a case class with a role parameter, instead of an object, should give you the desired effect:
case class UserAction(role: String) extends ActionBuilder[UserRequest] with ActionTransformer[Request, UserRequest] {
override def transform[A](request: Request[A]) = ...
}