adamnfish
adamnfish

Reputation: 11255

Testing scala Play (2.2.1) controllers with CSRF protection

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.

Upvotes: 4

Views: 2367

Answers (6)

David Keen
David Keen

Reputation: 633

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.

Upvotes: 1

Argurth
Argurth

Reputation: 644

For those who might be interested, I created a trait for play 2.5.x : https://stackoverflow.com/a/40259536/3894835

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

val fakeRequest = addToken(FakeRequest(/* params */))

Upvotes: 1

haui
haui

Reputation: 156

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

Upvotes: 1

Nathan
Nathan

Reputation: 1478

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);
}

Upvotes: 1

plade
plade

Reputation: 541

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()

Upvotes: 4

James Roper
James Roper

Reputation: 12850

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.

Upvotes: 4

Related Questions