How to bypass antiforgery token validation on form post in integration tests

Viewed 2698

I've an ASP.NET core 2.2 MVC app which exposes a Login page with a basic form (username/password). The controller action is protected by the AntiForgeryTokenAttribute, and the hidden __RequestVerificationToken is added by MVC.

I'm writing Integration Tests using TestServer and I want to send the form and see if I get 302 status code, but I couldn't find any valid option.

One option I evaluated was to do the GET, extract the __RequestVerificationToken and then submit the tpoken as part of the form. Howerer this won't work as I am missing the cookie (I believe). TestServer.CreateClient doesn't support any handler, so I can't add cookies.

Is there a way to test this ?

Thanks!

2 Answers

so there are two things needed:

  1. during the page GET: get the cookie from the Headers and extract the __RequestVerification
  2. when submitting the form: add the cookie in the Headers and add __RequestVerification to the model

1. GET

You can extract the token using:

headers.FirstOrDefault(x => x.Key == "Set-Cookie").Value.First().Split(" ")[0];

// The cookie we are looking for is .AspNetCore.Antiforgery.<unique guid>=<unique guid>
var tokenCookie = cookieContent.Split("=");
var name = tokenCookie[0];
var value = tokenCookie[1];

You can extract the __RequestVerification using Nuget package HtmlAgilityPack and then doing:

var htmlDoc = new HtmlDocument();
htmlDoc.LoadHtml(htmlContent);
var tokenValue = htmlDoc.DocumentNode.SelectSingleNode("//input[@name='__RequestVerificationToken']")
                .Attributes.Where(x => x.Name == "value").Select(x => x.Value).First();

where htmlContent is HttpResponse.Content.ReadAsStringAsync();

2. POST

When you create the form, add the __RequestVerificationToken:

new FormUrlEncodedContent(new List<KeyValuePair<string, string>>
{
    ... your stuff here
    new KeyValuePair<string, string>("__RequestVerificationToken", token)
        });
}

then when sending the request:

var request = new HttpRequestMessage(HttpMethod.Post, endPoint)
{ Content = form };

request.Headers.Add("Cookie", $"{cookie.Name}={cookie.Value}");

await client.SendAsync(request);

where client is HttpClient created using TestServer.CreateClient.

Hope this helps somebody else!

For a nicer way to get the required tokens and field names without resorting to scraping HTML from pages, I've written up a blog post and same sample code about how you can use Application Parts to achieve the same goal:

  1. Blog post
  2. Sample code
Related