Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Play Framework 2.6 CSRF and Session

I got strange issue. I'm implementing cart functionality on my website and I use session to store cart positions. I have a POST action to add new position to cart, and I have CSRF filter enabled to secure website. I call it with ajax on a product page, so first call is okay, but second says Unauthorized and in logs there are [CSRF] Check failed because no token found in headers for /cart. But it has. I call it with:

 $("form").submit(function(e){
        e.preventDefault();
        $.ajax({
            url: '/cart',
            method: 'POST',
            data: getCartPosition(),
            beforeSend: function(xhr){xhr.setRequestHeader('Csrf-Token', $('input[name=csrfToken]').val());},
            success: function (data, textStatus) {
                alert('Added!');
            },
            error: function (error) {
                alert('Error!');
            }
        });
    });

and I put CSRF token in template somewhere:

@CSRF.formField

and it's in request:

enter image description here

I have enabled this in config

play.filters.csrf.bypassCorsTrustedOrigins=true 
play.filters.hosts {
  # Allow requests to example.com, its subdomains, and localhost:9000
  allowed = ["localhost:9000", "localhost:4200"]
}

But what is strange that it seems it puts csrfToken in session, because after failed request I have session like this

Session(Map(cart -> {"positions":
[{"trackId":1},{"trackId":24},{"trackId":20}]}, 
username -> user, 
token -> 0639d0b0-e7c8-4e82-9aad-2a43044e72db, 
csrfToken -> e705413843ea96a6491a0e9e800ba36a712c4f70-1506542471068-0baeef7535eb9c889fb6fed2))

Idk why it's there, my add2cart action looks like:

private def cartAction(addToCartForm: Form[CartPosition], action: (Cart, CartPosition) => Cart)(implicit request: UserRequest[Any]) = {
    addToCartForm.fold(
      _ => BadRequest("Error!"),
      position => {
        getCart match {
          case Some(cart) => Ok("Ok").withSession("cart" -> Json.toJson(action(cart, position)).toString(), "username" -> request.session.get("username").getOrElse(""), "token" -> request.session.get("token").getOrElse(""))
          case _ => Ok("Ok, no").withSession("cart" -> Json.toJson(action(Cart(Seq.empty), position)).toString())
        }
      }
    )
  }

def addToCart() = guestAction { implicit request =>
    cartAction(addToCartForm.bindFromRequest, addCartPos)
  }

and addCartPos just adds position to json

like image 345
cutoffurmind Avatar asked Sep 27 '17 20:09

cutoffurmind


2 Answers

I've got same issue with Play 2.7.3.

In my case, the form is generated by the Twirl with the csrf token and because I'm using ajax to submit the form, I've copied the csrf token from the rendered form and pass it to the ajax header as writen in the Play's documentation.

The form can be submitted multiple times so I need to updated the token. Therefore I'm passing through ajax response new csrf token taken in the controller from play.filters.csrf.CSRF.getToken.get.value.

But unfortunatelly, the second submission failed as cutoffurmind mentioned.

And the fix is as described by Knut Arne Vedaa to add new token to the session. I did it by the withSession method.

So the controller response is looking like this:

 Ok(Json.obj(
    "status" -> (user != None),
    "notif" -> "Success login",
    "data" -> Map( 
       "adminUrl" -> "www.something ...", 
       "csrf" -> play.filters.csrf.CSRF.getToken.get.value
    )
 )).withSession(
    "uid" -> user.getOrElse(User()).id.toString,
    "csrfToken" -> play.filters.csrf.CSRF.getToken.get.value
 )

It doesn't look like an issue as Play Framework doesn't have session data kept on the server, so it is logical that the token has to be updated in the client site after the ajax request. The main issue is that it is not mentioned in the documentation (in the CSRF ajax section), what could be handy as people simply doesn't read the doc from A to Z in expected order.

like image 78
domino Avatar answered Oct 16 '22 06:10

domino


In my case the solution was to set the play.filters.csrf.cookie.name config option to a value other than null:

play.filters.csrf.cookie.name = csrf_token
like image 40
Vlad Avatar answered Oct 16 '22 06:10

Vlad