I basically need to protect against Cross Site Request Forgery in my Web API Controller that's a part of an MVC application. I am open to any ideas. At this point, I have an MVC View that displays an Esri map using ArcGIS for JavaScript API. The user creates a route on the map, and the route and various features it crosses can be saved via an AJAX POST. The View does not have a form. This is because all data that I POST to the server is in memory and not visible on screen (or in hidden fields).
So, I have the following in my MVC View to get antiforgery tokens:
@functions{
public string GetTokenHeaderValue()
{
string cookieToken, formToken;
AntiForgery.GetTokens(null, out cookieToken, out formToken);
return cookieToken + ":" + formToken;
}
}
I had this hidden input in the View, but realized this is bad since it has both the 'form token' and cookie token used with AntiForgery.Validation:
<input type="hidden" id="antiforgeryToken" value="@GetTokenHeaderValue()" />
Then, in a separate JavaScript file (not in script tag in View), I am doing a Http POST to my Web API Controller. This is where I add the tokens to the request headers:
var headers = {};
headers['RequestVerificationToken'] = $("#antiforgeryToken").val();
// Ajax POST to Web API Controller
$.ajax({
async: true,
url: '/api/RouteData',
type: 'POST',
headers: headers,
data: requestData,
dataType: 'json',
error: function (xhr, statusText, errorThrown) {
console.log('Error saving route data! ' + errorThrown);
},
success: function (result) {
}
});
NOTE: the data that is getting POSTed in the body is all in memory inside a custom Dojo widget (since the page displays an Esri map using ArcGIS for JavaScript). There is not a form on the page since the user doesn't enter data.)
Tying it all together on the server side in a Web API Controller:
[System.Web.Http.HttpPost]
[ResponseType(typeof(RouteData))]
public async Task<IHttpActionResult> PostRouteData(RouteDataViewModel routeDataVM)
{
try
{
HttpRequestMessage httpRequestMessage = HttpContext.Current.Items["MS_HttpRequestMessage"] as HttpRequestMessage;
ValidateRequestHeader(httpRequestMessage);
}
catch (Exception ex)
{
_logger.Log(LogLevel.Error, ex, ex.Message);
throw;
}
// Now that we know user is who they say they are, perform update
}
void ValidateRequestHeader(HttpRequestMessage request)
{
string cookieToken = "";
string formToken = "";
IEnumerable<string> tokenHeaders;
if (request.Headers.TryGetValues("RequestVerificationToken", out tokenHeaders))
{
string[] tokens = tokenHeaders.First().Split(':');
if (tokens.Length == 2)
{
cookieToken = tokens[0].Trim();
formToken = tokens[1].Trim();
}
}
AntiForgery.Validate(cookieToken, formToken);
}
AntiForgery.Validate is what validates the tokens.
I've seen this SO post, which has given me some ideas, but didn't quite solve the issue for me. A lot of credit is due to this post on ASP.net as well.
What makes this different for me (I think) is that my JavaScript is in a separate file and cannot call the server-side Razor function to get the antiforgery tokens, right? Any ideas on how to protect against CSRF without a form?
To prevent CSRF attacks, use anti-forgery tokens with any authentication protocol where the browser silently sends credentials after the user logs in. This includes cookie-based authentication protocols, such as forms authentication, as well as protocols such as Basic and Digest authentication.
ajaxSetup({ headers: { 'X-CSRF-TOKEN': $('meta[name="csrf-token"]'). attr('content') } }); Hope this is helpful!
A CSRF token is a unique, secret, unpredictable value that is generated by the server-side application and transmitted to the client in such a way that it is included in a subsequent HTTP request made by the client.
The most common approach to defending against CSRF attacks is to use the Synchronizer Token Pattern (STP). STP is used when the user requests a page with form data: The server sends a token associated with the current user's identity to the client. The client sends back the token to the server for verification.
CSRF protection is something that you should enact to do exactly that, protect from cross site request forgery.
A quick overview of CSRF is this:
Cross-Site Request Forgery (CSRF) is an attack that forces an end user to execute unwanted actions on a web application in which they're currently authenticated. CSRF attacks specifically target state-changing requests, not theft of data, since the attacker has no way to see the response to the forged request. With a little help of social engineering (such as sending a link via email or chat), an attacker may trick the users of a web application into executing actions of the attacker's choosing.
The following would be an example of a CSRF attack against your Web API if it were unprotected:
<form action="http://yourapi.com/api/DeleteAccount">
<input type="text" name="id" />
<input type="submit" />
</form>
By posting this form from a compromised website, if you were logged into your Web API using cookie authentication (as an example) as an administrative account, it may well be able to delete accounts from your system.
This happens because when you're redirected to your API, your browser is passing across the cookie which is stored against the yourapi.com
domain, thus authenticating the user and performing the required action. This attack vector is different from using, for example, a bearer token, to authenticate with your API as the third-party would have no knowledge of this token.
As you've rightly stated and implemented, the use of an anti-forgery token can protect from this as a generated token sent down to the client in a web response, and then sent back up by the client and validated to ensure that it is an allowed request from somewhere we expect. Like a bearer token, a third-party could have no knowledge of the token that you have sent down in your request from the server. In this respect, what you have implemented is absolutely correct and passing the token to the client is just how we would expect it to work.
In ASP.NET MVC (sans API) this is implemented as follows:
<form .. />
@Html.AntiForgeryToken()
</form>
.. and validated server side with:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Action(MyModel model)
{
// ..
}
When you call @Html.AntiForgeryToken()
, the method both sets a cookie (containing the cookie token) and sends it down to the client, and also generates an <input type="hidden" ..>
tag to send down to the client. These are then both sent back up and validated server side.
Armed with that knowledge, you can see that your concern about sending both the "cookie token" and "form token" is unfounded as this is what happens anyway.
I do see your cause for concern, but what you're trying to mitigate appears to be a Man in the Middle (MitM) attack rather than CSRF. To work around a large proportion of MitM attacks you should ensure that your site / API are both running over HTTPS. If your client is still susceptible to a MitM attack, your API would probably be of least concern to an attacker.
After some discussion with others, it appears that the solution I've implemented is actually alright. Yes, both tokens are in hidden input fields. However, by the very nature of what CSRF is trying to protect against - another site trying to POST on your behalf - this solution works just fine. If I'm on my site, authenticated, and browse to another site that tries to make a POST on my behalf, the site will not have the necessary tokens.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With