Prevent Cross-Site Request Forgery In ASP.NET Core
If you worked with ASP.NET MVC applications before, you are probably aware of
Cross-site request forgery (CSRF / XSRF) attacks. You might have also used
anti-forgery token based approach to prevent them. ASP.NET Core uses a similar
approach but there are a few differences that you should be aware of. To that
end this article briefly discusses the CSRF attack and ASP.NET Core way to
prevent them.
Although we won't discuss CSRF attacks in detail in this article, let's take
a simplified scenario to understand them. If you know what CSRF is you can skip
to the code level discussion.
Suppose you are logged in to a website. As a part of the login process the
website has issued you an authentication token in the form of a cookie. So, your
browser has this cookie with it. Now somehow you land on a malicious website.
May be you clicked a hyperlink from an email or something similar. Now the
malicious website cleverly issues requests (say POST requests) to the website
you logged in earlier. This is perfectly possible because that's how web and
HTML works. However, the malicious website attempts to tamper with the data by
sending requests that may insert or delete data in the system. Since you are
already authenticated with the website your requests might be successfully
executed. This is certainly undesirable and should be prevented.
Luckily, ASP.NET Core provides us means to prevent such attacks. Let's see
how.
Consider the following form tag helper
<form asp-action="Process"
asp-controller="Home"
asp-antiforgery="true"
method="post">
</form>
The form helper uses asp-action and asp-controller attributes to indicate
that the form will be handled by the Process() action of the HomeController. The
form tag helper also has asp-antiforgery attribute and its value is true.
Setting this attribute to true emits a hidden form field containing the
anti-forgery token. The following markup shows how this hidden field looks like
in the browser's view source window:

As you can see the __RequestVerificationToken hidden form field has been
added for you inside the form. This token needs to be validated inside your
controller (more about that in a minute).
Actually adding asp-antiforgery attribute explicitly is not always necessary.
If the form tag helper has its method attribute set to POST, ASP.NET Core
automatically emits the verification token for you. Here is the example:
<form asp-action="Process"
asp-controller="Home"
method="post">
</form>
And this is what the browser receives:

Same as before. Isn't it?
Note that if form's method is GET then the anti-forgery token is not emitted.
<form asp-action="Process"
asp-controller="Home"
method="get">
</form>
And here is the output this time:

Also note that you can set asp-antiforgery attribute to false to disable the
emission of the verification token.
<form asp-action="Process"
asp-controller="Home"
asp-antiforgery="false"
method="post">
</form>
And the following output proves that it is indeed disabled.

Ok. So far so good. We have completed half of the story. The remaining part
is to actually verify this token in the controller. Our intentions is that an
action under consideration - Process() in this case - should be executed only if
the anti-forgery token is verified successfully. If this token is tampered with
then the verification will fail and an exception is thrown.
To accomplish this task you can use three attributes :
- [ValidateAntiForgeryToken]
- [AutoValidateAntiforgeryToken]
- [IgnoreAntiforgeryToken]
The [ValidateAntiForgeryToken] attribute ensures that an action is invoked
only if the incoming request contains a valid anti-forgery token. This attribute
validates all requests irrespective of the HTTP verb. You can apply this
attribute to actions or controller. If applied to an action requests for that
action are validated. If applied on controller requests for all of its actions
are validated.
The [AutoValidateAntiforgeryToken] attribute is similar to the [ValidateAntiForgeryToken]
attribute but doesn't validate GET, HEAD, OPTIONS, and TRACE requests. This
attribute can also be applied to actions or controllers.
The [IgnoreAntiforgeryToken] attribute is used to skip validation for an
action. This comes handy when you have applied [ValidateAntiForgeryToken] or [AutoValidateAntiforgeryToken]
on the controller and want to skip validation just for a few actions.
Now, let's use these attributes in HomeController. The following code shows
the Process() action with [ValidateAntiForgeryToken] or [AutoValidateAntiforgeryToken]
applied.
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Process()
{
return View();
}
[HttpPost]
[AutoValidateAntiforgeryToken]
public IActionResult Process()
{
return View();
}
As you can see these attributes are being used at an action level. The
following code shows how these attributes can be applied to a controller.
[ValidateAntiForgeryToken]
public class HomeController : Controller
{
public IActionResult Index()
{
return View();
}
[HttpPost]
public IActionResult Process()
{
return View();
}
}
And....
[AutoValidateAntiforgeryToken]
public class HomeController : Controller
{
public IActionResult Index()
{
return View();
}
[HttpPost]
public IActionResult Process()
{
return View();
}
}
Finally this is how you can use [IgnoreAntiforgeryToken] attribute.
[AutoValidateAntiforgeryToken]
public class HomeController : Controller
{
public IActionResult Index()
{
return View();
}
[HttpPost]
[IgnoreAntiforgeryToken]
public IActionResult Help()
{
return View();
}
}
To read more about cross-site request forgery and its prevention you may read
the official documentation
here.
That's it for now! Keep coding!!