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?
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)
}
}
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;
}
}
Related
I am trying to add authentication to my Play Framework single page app.
What I would like to have is something like:
def unsecured = Action {
Ok("This action is not secured")
}
def secured = AuthorizedAction {
// get the authenticated user's ID somehow
Ok("This action is secured")
}
For a traditional web app, I had previously done this, following Play Framework docs:
def authenticate = Action { implicit request =>
loginForm.bindFromRequest.fold(
formWithErrors => BadRequest(views.html.login(formWithErrors)),
user => {
Redirect(routes.Application.home).withSession(Security.username -> user._1)
}
)
}
def logout = Action {
Redirect(routes.Auth.login).withNewSession.flashing(
"success" -> "You are now logged out."
)
}
and the Authorized Action is extending ActionBuilder as follows:
object AuthorizedAction extends ActionBuilder[Request] with Results {
/**
* on auth success: proceed with the request
* on auth failure: redirect to login page with flash
*/
def invokeBlock[A](request: Request[A], block: (Request[A]) => Future[Result]) = {
// TODO: is "isDefined" enough to determine that user is logged in?
if(request.session.get("username").isDefined) {
block(request)
}
else {
Future.successful(Redirect(routes.Auth.login).flashing(
"failure" -> "You must be logged in to access this page."
))
}
}
}
For single page applications however, this approach doesn't exactly work anymore.
This article by James Ward explains how the new approach is to be designed, and includes a Java implementation:
Securing SPA and rest services
The implementation was redone in Scala by Marius Soutier: Securing SPA in Scala
In his example, he implements a Security trait:
trait Security { self: Controller =>
val cache: CacheApi
val AuthTokenHeader = "X-XSRF-TOKEN"
val AuthTokenCookieKey = "XSRF-TOKEN"
val AuthTokenUrlKey = "auth"
/** Checks that a token is either in the header or in the query string */
def HasToken[A](p: BodyParser[A] = parse.anyContent)(f: String => Long => Request[A] => Result): Action[A] =
Action(p) { implicit request =>
val maybeToken = request.headers.get(AuthTokenHeader).orElse(request.getQueryString(AuthTokenUrlKey))
maybeToken flatMap { token =>
cache.get[Long](token) map { userid =>
f(token)(userid)(request)
}
} getOrElse Unauthorized(Json.obj("err" -> "No Token"))
}
}
Functions are now secured like this instead of a plain Action:
def ping() = HasToken() { token => userId => implicit request =>
user.findByID (userId) map { user =>
Ok(Json.obj("userId" -> userId)).withToken(token -> userId)
} getOrElse NotFound (Json.obj("err" -> "User Not Found"))
}
where .withToken is defined as:
implicit class ResultWithToken(result: Result) {
def withToken(token: (String, Long)): Result = {
cache.set(token._1, token._2, CacheExpiration)
result.withCookies(Cookie(AuthTokenCookieKey, token._1, None, httpOnly = false))
}
def discardingToken(token: String): Result = {
cache.remove(token)
result.discardingCookies(DiscardingCookie(name = AuthTokenCookieKey))
}
}
I am not liking how complex the "ping" function above has become, and would have preferred to use an Action Builder (like the first example), where auth failure is caught and dealt with at a single point. (as of now, if I want to secure functions ping2 and ping3, each one has to check whether the user is found and deal with the "not found" case)
I have tried to put together an action builder, inspired by Marius' implementation, most particularly his use of the cacheApi which is necessary.
However the AuthorizedAction is an object, and cacheApi needs to be injected (so need to change the object to singleton class), or cannot be declared in an object without being defined.
I also feel like the AuthorizedAction needs to remain an object, in order to be used as:
def secured = AuthorizedAction {
Would anyone please clear up the confusion, and possibly help with some implementation details?
Thanks a lot
The simplest way in my opinion is to go with ActionBuilder. You can define an action builder as a class (and pass it some dependencies) or as an object.
First you'll need to define a type a request that will contain the information about the user:
// You can add other useful information here
case class AuthorizedRequest[A](request: Request[A], user: User) extends WrappedRequest(request)
Now define your ActionBuilder
class AuthorizedAction(userService: UserService) extends ActionBuilder[AuthorizedRequest] {
override def invokeBlock[A](request: Request[A], block: (AuthorizedRequest[A]) ⇒ Future[Result]): Future[Result] = {
request.headers.get(AuthTokenHeader).orElse(request.getQueryString(AuthTokenUrlKey)) match {
case Some(token) => userService.findByToken(token).map {
case Some(user) =>
val req = AuthorizedRequest(request, user)
block(req)
case None => Future.successful(Results.Unauthorized)
}
case None => Future.successful(Results.Unauthorized)
}
}
}
Now you can use it in your controller:
val authorizedAction = new AuthorizedAction(userService)
def ping = authorizedAction { request =>
Ok(Json.obj("userId" -> request.user.id))
}
Given the following EssentialAction...
object MyController extends Controller {
...
def HasToken(action: Token => EssentialAction) = EssentialAction { request =>
...
// this doesn't compile
val body = request.body match {
case json: JsValue => json.toString
case _ => ""
}
// calculate hash with body content here
...
}
// here is an authenticated action
def getUser(userId: Strign) = HasToken { token =>
Action(parse.json) { request =>
request.body.validate[User] match {
...
}
}
}
}
... how do I get the body of the request without parsing it?
I don't want and I don't need to parse the request body in HasToken since the body is going to be parsed in action getUser. I just need the raw content of the body to calculate a hash.
The code in HasToken doesn't compile because request is of type RequestHeader whereas I need a Request, which defines body.
Will this work for you ?
object MyController extends Controller {
// Your HasToken Action
def Authenticate(action: Token => EssentialAction) = EssentialAction { requestHeader =>
// ... execute logic to verify authenticity using requestHeader
}
// Your action to validate tampering of request body and validity of JSON
def Validate[A](action: Token => Request[A]) = Action(parse.json) { request =>
val body = request.body
body match {
case json: JsValue => json.toString
case _ => ""
}
// calculate hash with body content here
body.validate[User] match {
// ...
}
}
def getUser(userId: Strign) = Authenticate { token =>
Validate { user =>
//.... Continue
}
}
}
Authentication only uses RequestHeader
Validation uses Request body. (Bonus: Body is only parsed once)
EDIT:
Question #1: I don't want to validate the body in Validate... since I need a generic validation mechanism that could be used everywhere regardless of the content type (e.g. user, message, etc.).
How about adding another type param (so that it is made generic):
def Validate[A, B](action: Token => Request[A])(implicit reads: Reads[B]) = Action(parse.json) { request =>
// ...
}
Question #2: Furthermore, if the token validation fails, the body don't have to be processed (that's important in case of file upload, which has to be performed if and only if the validation succeeded). That's way, in my opinion, the best option would be to read the raw content of the body in Validate.
This can be easily achieved:
def Validate[A, B](action: Token => Request[A])(implicit reads: Reads[B]) = Action(parse.json) { request =>
val body = request.body
body match {
case json: JsValue => json.toString
case _ => ""
}
// calculate hash with body content here and figure out if the body is tampered
if (bodyIsNotTampered) {
body.validate[B] match {
// ...
}
} else {
// log and return Future.successful(BadRequest)
}
}
EDIT 3: Full solution:
import play.api.libs.json.{Json, JsValue, Format}
object CompilationUtils {
class Token
case class User(name: String)
implicit val UserFormat = Json.format[User]
def authenticate = new Token // authentication logic
def isTampered(body: JsValue) = {
val bodyAsStr: String = Json.stringify(body)
// calculate hash with body content here
false
}
}
object MyController extends Controller {
import CompilationUtils._
// Your HasToken Action
def Authenticate(action: Token => EssentialAction) = EssentialAction { requestHeader =>
action(authenticate)(requestHeader) // your execute logic to verify authenticity using requestHeader
}
// Your action to validate tampering of request body and validity of JSON
def Validate[A, B](request: Request[A])(implicit formatA: Format[A], formatB: Format[B]): Either[Result, B] = {
val body = request.body
val bodyAsJsValue = Json.toJson(body)
if (!isTampered(bodyAsJsValue)) {
bodyAsJsValue.validate[B].fold(
valid = res => Right(res),
invalid = err => Left(BadRequest(err.toString))
)
} else {
Left(BadRequest) // Request Tampered
}
}
def getUser(userId: String) = Authenticate { token =>
Action(parse.json) { request =>
Validate(request).fold(
badReq => badReq,
user =>
// continue...
Ok("")
)
}
}
}
I'm using the example code from Play's ScalaOAuth documentation to authenticate to the twitter API. My full code is here:
object Twitter extends Controller {
val KEY = ConsumerKey("redacted")
val TWITTER = OAuth(ServiceInfo(
"https://api.twitter.com/oauth/request_token",
"https://api.twitter.com/oauth/access_token",
"https://api.twitter.com/oauth/authorize", KEY),
true)
def authenticate = Action { request =>
request.getQueryString("oauth_verifier").map { verifier =>
val tokenPair = sessionTokenPair(request).get
// We got the verifier; now get the access token, store it and back to index
TWITTER.retrieveAccessToken(tokenPair, verifier) match {
case Right(t) => {
// We received the authorized tokens in the OAuth object - store it before we proceed
Redirect(routes.Application.timeline).withSession("token" -> t.token, "secret" -> t.secret)
}
case Left(e) => throw e
}
}.getOrElse(
TWITTER.retrieveRequestToken("http://localhost:9000/auth") match {
case Right(t) => {
// We received the unauthorized tokens in the OAuth object - store it before we proceed
Redirect(TWITTER.redirectUrl(t.token)).withSession("token" -> t.token, "secret" -> t.secret)
}
case Left(e) => throw e
})
}
def sessionTokenPair(implicit request: RequestHeader): Option[RequestToken] = {
for {
token <- request.session.get("token")
secret <- request.session.get("secret")
} yield {
RequestToken(token, secret)
}
}
}
I spin up an actor that I want to use to search the Twitter API. I also use an edited version of their controller in that documentation to do this:
class TwitterParse extends Actor {
private var buildURL: String = _
private val urlBoilerPlate = "https://api.twitter.com/1.1/users/search.json?q="
private val pageCount = "&page=1&count=3"
def receive = {
case rawName: (Long, String) => {
buildURL = urlBoilerPlate + rawName._2 + pageCount
Twitter.sessionTokenPair match {
case Some(credentials) => {
WS.url(buildURL)
.sign(OAuthCalculator(Twitter.KEY, credentials))
.get
.map(result => println(result.json))
}
}
}
When I compile I get: "Cannot find any HTTP Request Header here" on the Twitter.sessionTokenPair call. This probably has to do with the fact that in their docs they include a
Action.async { implicit request =>
before calling sessionTokenPair. How would I fix this so it works in the Actor?
Thanks in advance
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)
}
}
}
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]
}
I am intercepting all the requests to my play application by overriding onRouteRequest method of GlobalSettings. Now, I need to send some data to the despatched action from here so that I dont perform all those calculations in all the actions. How do I set an attribute to the request (play.api.mvc.RequestHeader) object that I pass to the super onRouteRequest method ?
For your need I don't thing using the onRouteRequest will work (elegantly at least).
But let's try to use a dedicated structure to intercept.
Here is how you could intercept the request, compute some generic stuff and pass it to Action
First of all, here is an Interceptor object that has a method intercept and a convenient method username :
object Interceptor {
def intercept[A, B](f: RequestHeader => Option[B], e: RequestHeader => Result)(action: B => Action[A]): Action[(Action[A], A)] = {
val bodyParser = BodyParser {
request =>
f(request) map {
b =>
val innerAction = action(b)
innerAction.parser(request).mapDone {
body => body.right.map(innerBody => (innerAction, innerBody))
}
} getOrElse {
Done(Left(e(request)), Input.Empty)
}
}
Action(bodyParser) {
request =>
val (innerAction, innerBody) = request.body
innerAction(request.map(_ => innerBody))
}
}
def username[A](check: RequestHeader => Option[String]): ((String) => Action[A]) => Action[(Action[A], A)] = intercept(check, r => Results.Unauthorized("not logged in"))
}
As you can see, the worker function intercept gives you the opportunity to compute some stuff based on the request content. Which computation result of type B might failed (Option), in that case a handler is there to tell what to do.
Having define what to compute, you can define your action using a function that takes a B and gives an Action[A].
The username method is just a simple predefined interceptor that can enables us to define how to retrieve the logged in username, just to illustrate.
Now here is how we can use both of them in your Controller
//index is defined for both GET and POST in routes, but fails on POST
// thanks to the interceptor that checks at first the used method
// the case mustn't be handled in the Action definition
def index = Interceptor.intercept(
/*check the method*/
request => if (request.method == "GET") Some(request.method) else None,
/*not a GET => bad request*/
request => BadRequest(request.method + " not allowed")
) { /*the computation result*/method => Action {
Ok("The method : " + method)
}
}
//this controller retrieve the username in the session and renders it in a OK response
def secured = Interceptor.username(r => r.session.get("username")) { username => Action {
Ok("You're logged in as " + username)
}
}
//this enables you to logged in => store in session
def login(u:String) = Action { request => {
Ok("Logged in as " + u) withSession(("username" -> u))
}
}
Now if you have a generic computation you can create your preconfigured interceptor (here I'm using a case class but simply defining a function that partially applies the interceptor is enough)
case class Intercept[B] (f: RequestHeader => Option[B], e: RequestHeader => Result) {
def apply[A](action: B => Action[A]) = Interceptor.intercept[A,B](f, e)(action)
}
val getInterceptor = Intercept[String](
request => if (request.method == "GET") Some(request.method) else None,
request => BadRequest(request.method + " not allowed")
)
def index2 = getInterceptor { method => Action {
Ok("Da method : " + method)
}
}
EDIT related to the comment:
Accordingly to your comment, here is how you could do using an interceptor (note that I've mocked up the host retrieval and checking)
Using hosted and anotherHosted, you'll be able to test this workflow:
/hosted/false?host=myhost => 404 because at first myhost is not cached and I provided false to the checked mockup
/hosted/true?host=myhost => not in cache but it will add it, and then no 404
/hosted/anotherHosted/false?host=myhost => in cache because it is hosted => no 404
/hosted/anotherHosted/false?host=notMyhost => 404
Here is the code
def getHost(request:RequestHeader) = request.queryString.get("host").get.head
def checkHost(host:String, b: Boolean) = b
val checkHosted = (b: Boolean) => Intercept[String](
request => {
val host = getHost(request)
Cache.getAs[String](host) match {
case x#Some(_) => x
case None => if (checkHost(host, b)) {
Cache.set(host, host)
Some(host)
} else None
}
},
request => NotFound(getHost(request) + "not hosted")
)
def hosted(b:String) = checkHosted(b.toBoolean) {
host => Action {
Ok("this host is ok : " + host)
}
}
def anotherHosted(b:String) = checkHosted(b.toBoolean) {
host => Action {
Ok("this host is ok : " + host)
}
}