Sometime back Sumit Maitra wrote a nice article about what are CSRF attacks and how to prevent them in ASP.NET MVC. He demoed how a CSRF hack can be engineered and executed in details. He also showed us how ASP.NET MVC provides the AntiRequestForgeryToken to prevent CSRF hacks.
To quickly recap, a CSRF hack involves posting data to a valid URL that often requires authentication. The hack depends on the fact that an authentication token is available in the browsers cache and when the bad URL does a post, the browser uses the cached credentials.
If we had a WebAPI backed web application, the same loop holes exist. Fact is, WebAPI with Basic Authentication enabled, the hack gets a little easier to execute because Browsers caches Basic Auth tokens to prevent prompting for authentication for each resource they are getting.
The solution is to have an AntiForgeryToken style implementation in Web API backed pages as well. So today we will see how we can piggy back on the MVC AntiForgeryToken implementation to thwart CSRF attacks.
A Web API service with Basic Authentication
A simple example of Basic Authentication is Windows Authentication. Today, we’ll use a Windows Authentication enabled web site to explore Cross Site Request Forgery (CSRF) risks in Web API. To do so, first create a new Web API project in Visual Studio.
Let’s call it WebApiAuthorizationToken. Select the project node in Solution Explorer and change the following properties
- Anonymous Authentication: Set it to ‘Disabled’
- Windows Authentication: Set it to ‘Enabled’
Now all you have to do is use the Authorize keyword on either your Controller or Action Method to protect your API call. Correct? Well yeah, kind of!
Setting up a Simple POST to a WebAPI Controller
Traditionally we use WebAPI with AJAX calls but for sake of simplicity, let’s assume we’ll make a HTTP POST that will change some value at the server. We can imagine it to be something as sensitive as Money Transfer from one account to another.
In the Index.cshtml, let’s replace the section that says “We Suggest the following” with just a markup for a Button to post an HTML Form
<div>
@using (Html.BeginForm("", "api/Values"))
{
<button id="post" name="post">Transfer Money</button>
}
</div>
As we can see, the From will by default POST to the ‘api/Values’ controller. To make our POST method ‘secure’, we decorate it with the [Authorize] attribute. To test, we simply run the application and hit the “Transfer Money” button. We put a break point in the POST method to confirm that the form is posting correctly.
Confirming Security
Just to confirm the security is indeed working, we go back to the project properties and reset the authentication to Anonymous and then run the app again. You will note that instead of navigating to the Post method, we are getting a download dialog like this
Let us start IE Developer Tools for the Index page, go to the ‘Network’ Tab and ‘Start Capturing’.
Now click on the “Transfer Money” button. You’ll see the following response
So as we can see, our Authorize attribute is doing the trick. We are happy that our Web API is secure… umm… kind of!
Bad Guy Site and Cross Site Posting
In the real world, the Form to post will be much more elaborate. Now let’s assume there is a “Bad Guy” who has mimicked this Form and has somehow figured out that the api/values URL handles a POST action. So now all he has to do it get you to click on a button that POSTs his data instead of yours. Even though your data is secured via Basic authentication, if you had recently logged in to the original Site, the browser will see that you are posting to a site who’s Authentication you already have and will naively send the Authentication cookie along with the Bad Request. Let’ see how this is done.
Adding the “Bad Guy Site” to the Project
Let’s add a new Web API project to the Solution and now call it the ‘BadSite’
Once the project is created we add the following in the Index.cshtml (again we’ll replace the suggestions section that comes by default).
<form action="http://localhost:57320/api/Values/" method="post">
<button id="post" name="post">Transfer Money</button>
</form>
Note the port number. This post is actually going to our original Site. Now build both the solutions and launch the Original Site. Next use the IIS Express tray icon to launch the BadSite in a different web browser as well
Now you have the scary scenario of both the sites looking exactly the same (except here the Bad Guy was nice enough to change the welcome banner).
Click on Transfer Money from the Original Site at port number 57320. Then click on the Transfer Money from the Bad Guy’s site at 57618. In both cases you will see the breakpoint in the /api/Values Post action method is hit. YIKES! Our so called Secure and Authorized site has been hacked into by a Bad guy.
Now let’s see what we can do to thwart the baddie.
Using an AntiForgeryToken to Prevent Cross Site Posting
Adding an AntiForgeryToken generates a Cryptographically valid hash at the server end which is split and a part is added as a hidden field, whereas the rest goes into a cookie. When data is posted, the Cookie and the Hidden Field are both sent back and if they are missing or they don’t match, the POST is rejected.
In MVC this happens automatically when you request for an AntiForgeryToken. In Web API, we have to do this check manually.
Enabling AntiForgeryToken
Update the Index.cshtml to request for an AntiForgeryToken as follows
<div>
@using (Html.BeginForm("", "api/Values", FormMethod.Post))
{
@Html.AntiForgeryToken()
<button id="postData" name="postData">Transfer Money</button>
}
</div>
Now when this from is posted back to Server, two things happen. We peek into the Request using Fiddler to see this
Notice two things
1. Cookie called __RequestVerificationToken
2. Form Data with key __RequestVerificationToken
As I mentioned above, these tokens are cryptographically significant values and are essentially matching pairs. The AntiForgery.Validate(string 1, string2) helper method actually validates the pair at the server end once the data is posted. So even if our BadGuy was able to post his set of __RequestVerificationTokens, they would fail the Validation because they were generated for a different site.
Validating the Token
To validate the token on the server side, we have to do the following
1. Retrieve the cookie
2. Retrieve the FormDataCollection
3. Pickup the values for __RequestVerificationToken from both.
4. Pass them to Validate method
5. On error throw an Unauthorized exception
The Code in the Post Action method thus becomes like this
[Authorize]
// POST api/values
public void Post([FromBody]string value)
{
CookieHeaderValue cookie = Request.Headers
.GetCookies(AntiForgeryConfig.CookieName)
.FirstOrDefault();
if (cookie != null)
{
Stream requestBufferedStream = Request.Content.ReadAsStreamAsync().Result;
requestBufferedStream.Position = 0;
NameValueCollection myform = Request.Content.ReadAsFormDataAsync().Result;
try
{
AntiForgery.Validate(cookie[AntiForgeryConfig.CookieName].Value,
myform[AntiForgeryConfig.CookieName]);
}
catch (Exception ex)
{
throw new HttpResponseException(
new HttpResponseMessage(HttpStatusCode.Unauthorized));
}
}
}
Now if the BadSite posts data to our Web API Method, they’ll get zich!
Generalizing the Solution using a Delegation Handler
If we wanted to generalize this solution, we could create a Delegation Handler and plug it into the Web API pipeline ensuring that all requests supply AntiForgeryToken details. Let’s quickly add a Handler and see how it works
1. Add a folder called Handlers in our Original Site
2. Add a new class called AntiForgeryTokenHandler and inherit it from DelegationHandler
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Web;
namespace WebApiAuthorizationToken.Handlers
{
public class AntiForgeryTokenHandler : DelegatingHandler
{
…
}
}
3. Next we override the SendAsync method of the DelegatingHandler.
protected async override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,
CancellationToken cancellationToken)
{
return await base.SendAsync(request, cancellationToken);
}
4. We then move the code from the Post method to the Handler’s SendAsync Method and modify it to send HttpResponse instead of throwing Exception
protected async override Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request,
CancellationToken cancellationToken)
{
bool isCsrf = true;
CookieHeaderValue cookie = request.Headers
.GetCookies(AntiForgeryConfig.CookieName)
.FirstOrDefault();
if (cookie != null)
{
Stream requestBufferedStream = request.Content.ReadAsStreamAsync().Result;
requestBufferedStream.Position = 0;
NameValueCollection myform = request.Content.ReadAsFormDataAsync().Result;
try
{
AntiForgery.Validate(cookie[AntiForgeryConfig.CookieName].Value,
myform[AntiForgeryConfig.CookieName]);
isCsrf = false;
}
catch (Exception ex)
{
return request.CreateResponse(HttpStatusCode.Forbidden);
}
}
if (isCsrf)
{
return request.CreateResponse(HttpStatusCode.Forbidden);
}
return await base.SendAsync(request, cancellationToken);
}
5. Now if we run the application and put a break point in the Handler, we will see that our application passes through the Handler and reaches the Post method, whereas the Bad Guy’s Post is returned back from the Delegation handler itself.

Whereas the bad guy gets to see this
Caveats and Gotchas
One always has Caveats, don’t they. Well, in our case here is a small list of important ones
1. By depending on Cookies, you are assuming all your clients will be able to present Cookies. Remember Web API services are used to support different type of clients.
2. More importantly the Key/Value generated should be successfully validated by the AntiForgery library.
3. At one point above, we have read the Request’s Content stream and reset its Position to 0.
Stream requestBufferedStream = request.Content.ReadAsStreamAsync().Result;
requestBufferedStream.Position = 0;
This works when we are using the default ‘Buffered’ content type. However, if the defaults are changed to Streaming content type, then the Stream Position cannot be reset. As a result if you read the content here, no one else will be able to read it down the pipeline.
There are multiple solutions to this, one of the is to not use the Content; instead copy the Field from the RequestBody into a header while doing an AJAX POST. Then instead of reading the content, we pick the values from the custom header and do the validation.
The AJAX call would look something like this.
$.ajax(
{
cache: false,
dataType: 'json',
type: 'GET',
headers: { "X-RVT": $('input[name="__RequestVerificationToken"]').val() },
contentType: 'application/json; charset=utf-8',
url: '/api/Values',
…
}
The corresponding change to the Validate method would be.
AntiForgery.Validate(cookie[AntiForgeryConfig.CookieName].Value,
request.Headers.GetValues("X-RVT").First());
Conclusion
We saw how we can expose a Cross Site Resource Forgery vulnerability if we were not careful with our public web services specially when using Basic Authentication. One way to plug the whole was to use ASP.NET MVC's AntiResourceForgery token mechanism and extend it to Web API via a delegating handler.
Another solution of course would be use a more robust security mechanism like OAuth.
Download the entire source code of this article (Github)
This article has been editorially reviewed by Suprotim Agarwal.
C# and .NET have been around for a very long time, but their constant growth means there’s always more to learn.
We at DotNetCurry are very excited to announce The Absolutely Awesome Book on C# and .NET. This is a 500 pages concise technical eBook available in PDF, ePub (iPad), and Mobi (Kindle).
Organized around concepts, this Book aims to provide a concise, yet solid foundation in C# and .NET, covering C# 6.0, C# 7.0 and .NET Core, with chapters on the latest .NET Core 3.0, .NET Standard and C# 8.0 (final release) too. Use these concepts to deepen your existing knowledge of C# and .NET, to have a solid grasp of the latest in C# and .NET OR to crack your next .NET Interview.
Click here to Explore the Table of Contents or Download Sample Chapters!
Was this article worth reading? Share it with fellow developers too. Thanks!
Suprotim Agarwal, MCSD, MCAD, MCDBA, MCSE, is the founder of
DotNetCurry,
DNC Magazine for Developers,
SQLServerCurry and
DevCurry. He has also authored a couple of books
51 Recipes using jQuery with ASP.NET Controls and
The Absolutely Awesome jQuery CookBook.
Suprotim has received the prestigious Microsoft MVP award for Sixteen consecutive years. In a professional capacity, he is the CEO of A2Z Knowledge Visuals Pvt Ltd, a digital group that offers Digital Marketing and Branding services to businesses, both in a start-up and enterprise environment.
Get in touch with him on Twitter @suprotimagarwal or at LinkedIn