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