Creating a mandatory Anti-Forgery token

Creating a mandatory Anti-Forgery token

by | 5 min read
Published:
Updated:

One of the things I love about ASP.Net, is that a lot of the hard work that is required for a creating a secure website has already been done for you. It usually only takes a couple of lines of code to add these features in which means there are no excuses for missing off important security measures.

Anti-Forgery Token

One of these features is the Anti-Forgery token and it can be added to your MVC website with just 2 lines of code. So what is an anti-forgery token? As the name suggests it is a token to prevent forgery! In the same way that someone might forge a signature to pretend to be someone else, it is possible for a malicious person to forge a request to your website without the request coming from your website.

So how is this done I hear you say? Well lets say you have a form on your website for changing user details such as name and email address, and a hacker wanted to change these to something else.

The hacker could create a form on another website which matches the request your website is expecting and post to the same URL. The entire form could be in hidden fields and posted via an Ajax request on page load making it invisible to the user.

If the user is already logged in to your website when the other website posts the form, your website treats it as a valid request and will change the user details to whatever the hacker wants.

So how do we get around this?

Well in the same way that 2 factor authentication works on something you know and something you have. The anti-forgery token works as the something you have (sorry about the poor analogy). The server places a hidden field with a populated anti-forgery token into your form. When a request is made to your website, the server checks for the presence of the anti-forgery token and if it doesn’t exist or doesn’t match the expected value an exception occurs.

As the hackers malicious form doesn’t know what the Anti-Forgery token is the request fails.

Adding an Anti-Forgery token

This all sounds great so how do you add this in? As i mentioned you only need 2 lines of code to add in an Anti-Forgery token, one in the view and one in the controller.

View

In the view you need to add in the anti-forgery token with @Html.AntiForgeryToken() inside your post form like this:

@using (Html.BeginForm("Login", "Account", new { ReturnUrl = ViewBag.ReturnUrl }, FormMethod.Post, new { @class = "form-horizontal", role = "form" }))
{      @Html.AntiForgeryToken()
.....
...

Controller

The controller then needs to have the [ValidateAntiForgeryToken] added to the post action.

// POST: /Account/Login
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Login(LoginViewModel model, string returnUrl)
{      if (!ModelState.IsValid)      {            return View(model);      }

If you look at the source code of your page you should now see the following in your form (with a different value of course):

<input name="__RequestVerificationToken" type="hidden" value="JN8mexoJ6sCyfy9TzagXr1DSmjk6au-5VfP9IN_EyLhkWwvd-w2HGJ5EzCW1e_W9nf3wpTQWG_bDgDFFhzLWU8EqAb_8uQtXTwvojSTe3541">

Ok so lets test it by changing the value in this hidden field and seeing what happens. <input name="__RequestVerificationToken" type="hidden" value="bad token">

Anti Forgery Exception

As shown above we get the expected exception that the anti-forgery token could not be decrypted. In production you will have custom errors turned on so the user will see a nice error message instead of a stack trace which is dangerous in itself.

Making the Anti-Forgery token mandatory

As you might have guessed from the title of this post, we aren’t finished yet. If you are working in a team it is possible that someone might forget to add the anti-forgery token into their new view and action. So what happens if you forget to add the token to the view?

Anti Forgery Not Present

As you would expect you get an exception saying that the token is missing. Great, but what happens if you forget to add the ValidateAntiForgeryToken to the action?

Nothing. No exception. It just allows it through.

Given that you have to add an attribute to every post action it is conceivable to think one could be missed. We can mitigate this issue by doing 2 things:

1.) Use an HTML Helper Extension for BeginForm to always add in the token. 2.) Write a unit test to check for the presence of the ValidateAntiForgeryToken on every Post action.

So how do we do this: 1.) Use an HTML Helper extension for BeginForm To create a HTML Helper extension we are going to create a new class called HtmlExtensions and place it somewhere in our project. You then need to add the following method:

public static MvcForm BeginFormWithToken( this HtmlHelper htmlHelper, string actionName, string controllerName, object routeValues, FormMethod method, object htmlAttributes)
{
    var form = htmlHelper.BeginForm(actionName, controllerName, routeValues, method, htmlAttributes);
htmlHelper.ViewContext.Writer.WriteLine(htmlHelper.AntiForgeryToken());
    return form;
}

Now I have only created one for the BeginForm method shown above but you will probably want to create one for each of the other overloads you use. You will then need to go through each of your views and change BeginForm to BeginFormWithToken. So now we have the Anti-Forgery token being added to every form.

2.) Write a unit test to check for the presence of the ValidateAntiForgeryToken To do this we have to use reflection to find all the controller actions with HttpPost and check that they have a ValidateAntiForgeryToken present. In the example below you will need to change MyWebApplication to the name of your Web project.

[TestMethod]
public void TestValidateAntiForgeryTokenAttributeOnAllPostActions()
{
    // Act
    var actions = Assembly.Load("MyWebApplication").GetTypes()
                        .Where(t => typeof(Controller).IsAssignableFrom(t))SelectMany(type => type.GetMethods()).Where(method => method.IsPublic &&
                        method.GetCustomAttributes(typeof(HttpPostAttribute), true).Any() &&
                        !method.GetCustomAttributes(typeof(ValidateAntiForgeryTokenAttribute), true).Any()).ToList();
    // Assert
    Assert.IsFalse(actions.Any(), actions.Any() ? $"The action '{actions[0].Name}' in the '{actions[0].DeclaringType.Name} ' controller is missing the ValidateAntiForgeryToken attribute." : string.Empty);
}

This test will fail if any of the actions are missing the validate attribute and will give the name of the first offender. Feel free to modify this. For example you might want to get it to print out all of the actions that are missing the Anti-Forgery token attribute.

If this post was helpful please leave a comment below.


🙏 Was this helpful? If you want to say thanks, I love coffee ☕️ , any support is appreciated.


ALSO ON ALEXHYETT.COM

Idempotency - What it is and How to Implement it

Idempotency - What it is and How to Implement it

  • 22 September 2023
When designing an API it is easy to think about the happy path of our applications but if you want to build a robust application and keep…
5 Design Patterns That Are ACTUALLY Used By Developers

5 Design Patterns That Are ACTUALLY Used By Developers

  • 08 September 2023
High-level programming languages have been around since the 1950s and since then programmers worldwide have been using code to solve all…
Domain-Driven Design: Simple Explanation

Domain-Driven Design: Simple Explanation

  • 28 April 2023
When you are trying to build complex software it is important that everyone is on the same page. Even though most of us prefer to work alone…
Monolithic vs Microservice Architecture - Which Should You Use?

Monolithic vs Microservice Architecture - Which Should You Use?

  • 17 March 2023
If you are starting to build a new application or you are working on an existing one you may be wondering whether you should be building a…
Hexagonal Architecture: What Is It and Why Do You Need It?

Hexagonal Architecture: What Is It and Why Do You Need It?

  • 17 February 2023
We all do our best to try and write clean code that is going to be easy to maintain in the future. As time goes on and the application gets…
Snake Case vs Camel Case vs Pascal Case vs Kebab Case

Snake Case vs Camel Case vs Pascal Case vs Kebab Case

  • 06 February 2023
Coming up with names for things when writing software is hard. You need to come up with a few words that neatly describe everything you have…
What is CRUD? CRUD Operations in APIs

What is CRUD? CRUD Operations in APIs

  • 01 February 2023
90% of software that you use every day are what we call CRUD applications. Most of the time we are using them without even realising it…
Why Developers Should Embrace No-Code Solutions

Why Developers Should Embrace No-Code Solutions

  • 09 January 2023
The best line of code is the one you didn’t have to write. As developers, we love writing code. At the end of the day, it is what we are…