Testing scala Play (2.2.1) controllers with CSRF protection
Asked Answered
P

6

4

I've been having some problems testing controllers that use Play's CSRF protection. To demonstrate this, I've created a very simple Play application that minimally exhibits the problem.

https://github.com/adamnfish/csrftest

The full details are on the README of that repository, but to summarise here:

Consider a controller that is designed to handle a form submission. It has a GET method that uses CSRFAddToken and a POST method that uses CSRFCheck. The former adds a CSRF Token to the request so that a form field can be put in the rendered view, containing the valid token. When that form is submitted, if the CSRF check passes and the submission is valid, something else will happen (typically a redirect). If the form submission is not valid, the form submission is re-shown along with any errors so the user can correct the form and submit again.

This works great!

However, in the tests we now have some problems. To test the controller you can pass a fake request to it in the test. The CSRF check itself can be skipped by adding the nocheck header to the fake request but the view cannot be rendered because no token available to generate the form field. The test fails with a RuntimeException, "Missing CSRF Token (csrf.scala:51)".

Given that it works when it's actually running but not in the tests, it seems like this must be a problem with the way FakeRequests are run in Play tests but I may be doing something wrong. I've implemented the CSRF protection as described at http://www.playframework.com/documentation/2.2.1/ScalaCsrf and the testing as described at http://www.playframework.com/documentation/2.2.1/ScalaFunctionalTest. I'd appreciate any pointers if anyone has managed to test CSRF protected forms.

Propel answered 7/11, 2013 at 14:24 Comment(0)
T
4

One solution is to test using a browser, eg Fluentlenium, as this will manage cookies etc, so the CSRF protection should all just work.

The other solution is to add a session to the FakeRequest so that it contains a token, eg:

FakeRequest().withSession("csrfToken" -> CSRF.SignedTokenProvider.generateToken)

Obviously if you're doing that a lot, you can create a help method to do that for you.

Tarpaulin answered 8/11, 2013 at 0:20 Comment(2)
I'm testing the controller directly using mocks and dependency injection so selenium-style integration tests aren't an option here. Adding a valid, generated, token to the session does stop the errors happening. Thanks very much! I still feel this reveals a bug in the way Play runs tests but I can take that up on the mailing list. Thanks again.Propel
Is there a way to do this in Java?Giefer
D
4

Bonus answer for those interested in Java: I got this to work in the Java version of Play Framework 2.2 by adding

.withSession(CSRF.TokenName(), CSRFFilter.apply$default$5().generateToken())

to fakeRequest()

Departure answered 25/3, 2014 at 8:16 Comment(1)
Thanks for sharing. I'm ill-inclined to dust of the Java Version of Play to test that so I'll take your word for it :-)Propel
C
1

Following on from @plade, I added a helper method to my base test class:

protected static FakeRequest csrfRequest(String method, String url) {
    String token = CSRFFilter.apply$default$5().generateToken();
    return fakeRequest(method, url + "?csrfToken=" + token)
        .withSession(CSRF.TokenName(), token);
}
Caucasus answered 27/5, 2014 at 3:7 Comment(1)
For play 2.5.X you need to use CSRFFilter.apply$default$3().generateToken(); and CSRFFilter.apply$default$1().tokenName()So
G
1

To those that are still interested: I managed to solve this problem globally by enabling CSRF protection in tests. The app will then create a token for every request that does not contain one. See my answer to this question

Gull answered 28/4, 2017 at 13:4 Comment(0)
D
1

For those who might be interested, I created a trait for play 2.5.x : https://mcmap.net/q/999878/-testing-request-with-csrf-token-in-play-framework-2-5-scala

You can then use it in your tests requests like the addToken{} of the controller :

val fakeRequest = addToken(FakeRequest(/* params */))
Dropkick answered 12/5, 2017 at 14:1 Comment(0)
I
1

I use the following method in my base integration test class:

def csrfRequest(method: String, uri: String)(implicit app: Application): FakeRequest[AnyContentAsEmpty.type] = {
  val tokenProvider: TokenProvider = app.injector.instanceOf[TokenProvider]
  val csrfTags = Map(Token.NameRequestTag -> "csrfToken", Token.RequestTag -> tokenProvider.generateToken)
  FakeRequest(method, uri, FakeHeaders(), AnyContentAsEmpty, tags = csrfTags)
}

Then you can use it in your tests where you would use FakeRequest.

Ironstone answered 6/7, 2017 at 11:40 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.