POST-ing to an ASP.NET MVC form with an anti-forgery token
I've had some issues trying to write an acceptance test that was registering a new user by POST-ing the required information to a MVC site and I got back these messages:
- The required anti-forgery cookie "__RequestVerificationToken" is not present.
- The required anti-forgery form field "__RequestVerificationToken" is not present.
- Validation of the provided anti-forgery token failed. The cookie "_RequestVerificationToken" and the form field "_RequestVerificationToken" were swapped.
Since it took me a bit of fiddling with the code before I managed to make it work, I thought I'd share the solution. The site is an ASP.NET MVC version 5 and I am trying to register a new user (POST-ing to the /Account/Register URL). The main issue you will encounter is having to extract two anti-forgery tokens, one from the cookies and one from the form, and then sending both of them in the appropriate places (cookies vs form field).
I have used LinqPad 5 with a reference to the HtmlAgilityPack NuGet package (to simplify extracting the needed information from the GET form). This is the code:
void Main()
{
Cookie[] cookies;
var html = Get("Account/Register", out cookies);
var root = Parse(html);
var formToken = GetFormToken(root);
var cookieToken = GetCookieToken(cookies);
var unique = Guid.NewGuid().ToString("N");
Post("Account/Register", new
{
Email = unique + "@example.com",
User = unique,
Password = unique,
ConfirmPassword = unique,
},
formToken, cookieToken);
}
private const string BASE_URL = "http://localhost:5972"; // replace as needed
private const string TOKEN_NAME = "__RequestVerificationToken";
private static string Get(string url, out Cookie[] responseCookies)
{
using (var web = new CookieAwareWebClient())
{
var result = web.DownloadString(BASE_URL + "/" + url);
responseCookies = web.ResponseCookies.Cast<Cookie>().ToArray();
return result;
}
}
private static string Post(string url, object body, string formToken = null, string cookieToken = null)
{
using (var web = new WebClient())
{
var data = GetPostData(body);
web.Headers["Content-Type"] = "application/x-www-form-urlencoded";
if (formToken != null)
data += "&" + TOKEN_NAME + "=" + formToken;
if (cookieToken != null)
web.Headers.Add(HttpRequestHeader.Cookie, TOKEN_NAME + "=" + cookieToken);
return web.UploadString(BASE_URL + "/" + url, data);
}
}
private static string GetPostData(object obj)
{
var kv = obj
.GetType()
.GetProperties(BindingFlags.Instance | BindingFlags.Public)
.Select(prop => prop.Name + "=" + WebUtility.UrlEncode(prop.GetValue(obj) + ""))
.ToArray();
return string.Join("&", kv);
}
private static HtmlNode Parse(string html)
{
var doc = new HtmlDocument();
doc.LoadHtml(html);
return doc.DocumentNode;
}
private static string GetFormToken(HtmlNode root)
{
var formToken = root.SelectSingleNode("//*[@name='" + TOKEN_NAME + "']");
return formToken.GetAttributeValue("value", null);
}
private static string GetCookieToken(IEnumerable<Cookie> cookies)
{
return cookies
.Where(it => it.Name == TOKEN_NAME)
.First()
.Value;
}
// from http://stackoverflow.com/a/29479390
public class CookieAwareWebClient : WebClient
{
public CookieContainer CookieContainer { get; }
public CookieCollection ResponseCookies { get; set; }
public CookieAwareWebClient()
{
CookieContainer = new CookieContainer();
ResponseCookies = new CookieCollection();
}
protected override WebRequest GetWebRequest(Uri address)
{
var request = (HttpWebRequest) base.GetWebRequest(address);
// ReSharper disable once PossibleNullReferenceException
request.CookieContainer = CookieContainer;
return request;
}
protected override WebResponse GetWebResponse(WebRequest request)
{
var response = (HttpWebResponse) base.GetWebResponse(request);
// ReSharper disable once PossibleNullReferenceException
ResponseCookies = response.Cookies;
return response;
}
}
Hope this helps someone.
Comments