Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

MVC5 Anti-forgery Validator for HTTP Headers

0.00/5 (No votes)
23 Feb 2015 1  
ASP MVC5 authorization filter to validate anti-forgery token for JSON type of requests

Introduction

ASP.NET MVC provides a simple and effective means to stop cross-site-request-forgery attack. However, this scheme is available only for form data submissions. The purpose of this article is to create an authorization attribute which protects against cross-site request forgery for JSON like requests.

Background

ASP.NET MVC provides a simple and effective means to stop cross-site-request-forgery attack. Briefly, it works like this: when the server expects a user to send some information to it, it passes on a special bit of information to the client (browser). At this point, this security information is known to both the genuine client and the web server.

When the genuine client (browser) sends the user's information, it also sends this special bit of information. The server compares the two, if they match, it means that the client sending the information is the original one and the request is genuine.

If a request is somehow sent to the server from another source, this special bit of information will not be present which is then detected by the server and further request processing halts preventing the attack.

Anti-Forgery in HTML Forms

  1. First, identify the controller method(s) you wish to protect. Apply the [ValidateAntiForgeryToken] attribute on them.
  2. Create the anti-forgery token in the form which is to be submitted to the server using the @Html.AntiForgeryToken(); helper. This creates a hidden form field whose name is ' __RequestVerificationToken' and value is the anti-forgery token. It also stores a corresponding token to compare against in the cookies collection named '__ __RequestVerificationToken'.

The ValidateAntiForgeryToken is an authorization filter (implements IAuthrizationFilter) and runs before the action method runs. If the request does not contain the necessary anti-forgery token, an exception is thrown and the action method will not execute.

Using the Code

Arriving at the main purpose of this post which to create such a scheme for non HTML form data - say you wish to protect a method which accepts a JSON string and no form elements. One option is to pass an anti-forgery token as a member of the JSON object itself and check this value manually but this is not reusable.

Another method could be to pass the anti-forgery token in the HTTP header and do something similar that ValidateAntiForgeryToken does. We'll consider this solution.

First, we create a filter attribute named ValidateAntiForgeryHeader which checks the anti-forgery token but reads the token from the HTTP header:

public class ValidateAntiForgeryHeader : FilterAttribute, IAuthorizationFilter
{
	public void OnAuthorization(AuthorizationContext filterContext)
	{
		string clientToken = filterContext.RequestContext.HttpContext.Request.Headers.Get
                             (KEY_NAME);
		if (clientToken == null)
		{
			throw new HttpAntiForgeryException(string.Format("Header does not contain {0}", 
                  KEY_NAME));
		}

		string serverToken = filterContext.HttpContext.Request.Cookies.Get(KEY_NAME).Value;
		if (serverToken == null)
		{
			throw new HttpAntiForgeryException(string.Format("Cookies does not contain {0}", 
                  KEY_NAME));
		}

		System.Web.Helpers.AntiForgery.Validate(serverToken, clientToken);
	}

	private const string KEY_NAME = "__RequestVerificationToken";
}

To perform the actual comparison of the anti-forgery token, we make use of AntiForgery class present in System.Web.Helpers namespace.

We can now apply this attribute just like the ValidateAntiForgeryToken on a method we want to protect:

[ValidateAntiForgeryHeader]
public JsonResult SendMessage(MyModel model)
{
	MyModel m = new MyModel { Sender = "System", 
                Message = "Thanks for your message " + model.Sender };
	return Json(m);
}

Now for the Client Side

We still use the @Html.AntiForgeryToken(); HTML helper to generate the hidden field with the token value. Let's create a simple form which does two things:

  • Makes a normal form submit if one clicks the "Submit Form" button
  • Makes a JSON request asynchronously if one clicks the "Send JSON" button
@{
    ViewBag.Title = "Test";
}

<h2>Test</h2>

@{
	IDictionary<string, object> attrs = new Dictionary<string, object>();
	attrs.Add("name", "TestForm");
	attrs.Add("id", "TestForm");
	attrs.Add("data-asynchAction", "/Home/SendMessage");
}

@using (@Html.BeginForm("SubmitTest", "Home", FormMethod.Post, attrs))
{
	@Html.AntiForgeryToken();
	<p>Sender</p> @Html.TextBox("Sender")
	<br />
	<p>Message</p>@Html.TextBox("Message")
	<br />
	<p>Anti forgey token:</p><input type="text" id="token" value="" />
	<hr />
	<input type="Submit" value="   Submit Form   " /> 
	<input type="button" value="   Send JSON    " id="sendJson" />
}
	
<br />

@section Scripts
{
	<script src="~/Scripts/App/common.js"></script>
	<script src="~/Scripts/App/antiforgery.js"></script>
}

The corresponding JavaScript file code contains the method to send the JSON request and receive a JSON response:

$("#sendJson").click(function (event) {
	event.preventDefault();
	console.log("clicked");	
	var o = { sender: "Sid", Message: "Hello World!" };
	var jsonStr = JSON.stringify(o);
	var actionUrl = $("#TestForm").attr("data-asynchAction");
	console.log(actionUrl);
	var token = getVerificationToken('TestForm');
	console.log(token);
	$.ajax({
		url: actionUrl,
		type: "POST",
		headers: {
			"__RequestVerificationToken": token
		},
		data: jsonStr,
		dataType: "json",		
		contentType: "application/json; charset=utf-8"
	}).done(function (jsonResponse) {
		if (jsonResponse != null) {
			alert(jsonResponse.Message);
		}
	});
});

Notice we are sending the request verification token as part of the HTTP headers. "getVerificationToken()" is a method which reads the value of the hidden field generated by @Html.AntiForgeryToken() and is present in the common.js file (see the source code).

Please note, the sample code present in the ZIP file reads the token from a TEXTBOX which contains the valid token generated by @Html.AntiForgeryToken(). This is just so that the value can be modified and tested with.

References

  • Chapters 7 & 15 from the book Professional ASP.NET MVC5 by Jon Galloway et all

Thank you!

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here