Preventing XSRF in AngularJS Apps with ASP.NET CORE Anti-Forgery Middleware

What is Cross Site Request Forgery (XSRF/CSRF)

Cross Site Request Forgery (XSRF/CSRF) is a type of a security breech where a hacker can trick the user into making unwanted requests to a web application where he/she is already authenticated. When a user is authenticated, his/her authentication cookie is stored in the browser’s storage. Since the cookie stays active unless the browser is closed and reopened, an authenticated user can be forced to make requests to the application from other unsafe applications too.

For example, assume that you are a customer of a bank which allows you to do online banking. If you have that kind of facility, then definitely you would want to make online transactions whenever you want. So, what you would do is you will log in to the online banking application then will make some transactions. Suppose while doing the transactions suddenly you’ve got a mail. This mail says that, “You won a jackpot! To claim that you have a go to a specific link to a web application where you have to press a big red button that says, Claim Million Dollar Now!”. Let me tell you what I would do in this scenario, I would leave everything and press the big red button because I want to be a millionaire. But not every day is a million-dollar day and maybe that red button wont do what its actually saying. Behind the scene that red button can cause a huge misfortune for us.

Since it’s a button, it is very much possible that it could be a submit button and behind the scene there is html form too with some fields which are hidden. Suppose that the hacker who made that unsafe application knew that when a user makes a transaction on you current online banking system, it hits a specific endpoint on the server. So, what the hacker would do is he/she will create an action attribute on the form with that endpoint location. Now, with all the excitement going on the mind of a simple user like me won’t even bother to log out from my currently logged in online banking system and press that red button. And that's where a request will be sent to my logged in application and it will be treated as a valid request since my authentication cookie is still alive. Making a simple request won’t do any harm but remember that we talked about some hidden fields too. Upon clicking on a submit button will make a request along with every form fields values to our application over HTTP. So, if a hacker wants to steal my money then he can easily mimic a credit type transaction to his account from mine with all the fields necessary in a HTTP request. The values can be sent in both GET(as a query string) and POST (in request body) requests. So, the form's method attribute can be set to any of those.

This is a very common attack and even the popular video website YouTube was vulnerable to CSRF in 2008.

In this tutorial, we will be making an AngularJS app with Asp.net core Web API on the backend. In this application a user can make some simple debit and credit transactions against an account. We will see how XSRF can cause unwanted transactions against the account and then show how we can prevent XSRF exploits.

Building the client

In Visual Studio 2015, create an ASP.NET Core Web Application and select the Web API template under ASP.NET CORE templates window. Note that this article assumes that you have the latest version (RTM 1.0.0) of ASP.NET CORE libraries installed in your PC.

Visual studio 2015 and .NET Core 1.0.0 SDK, both of them are available for download in the following links,

.NET Core SDK - https://www.microsoft.com/net/core

Visual Studio 2015 - https://www.visualstudio.com/en-us/downloads/download-visual-studio-vs.aspx

We need three additional .NET core library dependencies, one for serving static files (Microsoft.AspNetCore.StaticFiles), one for creating the APIs (Microsoft.AspNetCore.Mvc) and the last one for adding antiforgey middleware (Microsoft.AspNetCore.Antiforgery). Open up the project.json file and update the dependencies node with the packages discussed above or just simply copy and paste the following configurations into your own package.json file


{
  "buildOptions": {
    "emitEntryPoint": true,
    "preserveCompilationContext": true
  },
  "dependencies": {
    "Microsoft.NETCore.App": {
      "version": "1.0.0",
      "type": "platform"
    },
    "Microsoft.AspNetCore.Server.IISIntegration": "1.0.0",
    "Microsoft.AspNetCore.Server.Kestrel": "1.0.0",
    "Microsoft.AspNetCore.StaticFiles": "1.0.0",
    "Microsoft.AspNetCore.Antiforgery": "1.0.0",
    "Microsoft.AspNetCore.Mvc": "1.0.0"
  },
  "frameworks": {
    "netcoreapp1.0": {
      "imports": [
        "dotnet5.6",
        "portable-net45+win8"
      ]
    }
  },
  "publishOptions": {
    "include": [
      "wwwroot",
      "web.config"
    ]
  },
  "runtimeOptions": {
    "configProperties": {
      "System.GC.Server": true
    }
  },
  "scripts": {
    "postpublish": [ "dotnet publish-iis --publish-folder %publish:OutputPath% --framework %publish:FullTargetFramework%" ]
  },
  "tools": {
    "Microsoft.AspNetCore.Server.IISIntegration.Tools": "1.0.0-preview2-final"
  }
}


To resolve client side dependencies, we will use bower. Before installing bower, add a Bower Configuration File in your project.

Now open up the command prompt and go to the root of your project directory and run this command,

npm install bower --save

After installing bower, you can right click on the Bower under the Dependencies node and select Manage Bower Packages. From there search and install Angular. Also install Bootstrap. By default, installing packages with bower will create a folder called lib under the wwwroot and store the client libraries there.

You can change the default directory of you client side library installation by changing the "directory" property in the .bowerrc file.

Add a index.html file under wwwroot and paste this markup below,


<!DOCTYPE html>
<html ng-app="app">
<head>
    <meta charset="utf-8" />
    <title>WHB - What Happened Bank!?!?!</title>
    <link href="lib/bootstrap/dist/css/bootstrap.css" rel="stylesheet" />
    <style>
        .container, .row {
            margin-top: 120px;
        }
    </style>
</head>
<body>
    <div class="container">
        <h1 class="text-center text-danger"><strong>WHB</strong></h1>
        <h3 class="text-center text-danger">What Happened Bank!?!?!</h3>
        <div class="text-center"><em>"you wouldn't know what just happened"</em></div>
        <div class="row" ng-controller="TransactionController as vm">
            <div class="col-md-5 col-md-offset-1">
                <div class="panel panel-success">
                    <div class="panel-heading">Debit/Credit on Account 1234</div>
                    <div class="panel-body">
                        <form>
                            <div class="form-group">
                                <label class="sr-only" for="creditAmount">Amount (in dollars)</label>
                                <div class="input-group">
                                    <div class="input-group-addon">$</div>
                                    <input type="number" class="form-control" id="creditAmount" placeholder="Amount" ng-model="vm.transactionAmount">
                                    <div class="input-group-addon">.00</div>
                                </div>
                            </div>
                            <button type="button" class="btn btn-warning" ng-click="vm.debit()">Debit</button>
                            <button type="button" class="btn btn-success" ng-click="vm.credit()">Credit</button>
                        </form>
                    </div>
                </div>
            </div>
            <div class="col-md-5">
                <div class="panel panel-warning">
                    <div class="panel-heading">Transactions</div>
                    <div class="panel-body">
                        <table class="table">
                            <thead>
                                <tr>
                                    <th>Acc. no</th>
                                    <th>Type</th>
                                    <th>Amount</th>
                                    <th>Balance</th>
                                </tr>
                            </thead>
                            <tbody>
                                <tr ng-repeat="transaction in vm.transactions">
                                    <td>{{transaction.account.accountNumber}}</td>
                                    <td>{{transaction.transactionType}}</td>
                                    <td>{{transaction.transactionAmount}}</td>
                                    <td>{{transaction.account.currentBalance}}</td>
                                </tr>
                            </tbody>
                        </table>
                    </div>
                </div>
            </div>
        </div>
    </div>

    <script src="lib/angular/angular.js"></script>
    <script src="app/app.js"></script>
    <script src="app/transaction/transaction.controller.js"></script>
    <script src="app/transaction/transaction.service.js"></script>
</body>
</html>

From the markup, as you can see we have an angular module named app (ng-app="app" ), a controller named TransactionController. We use the as syntax to be able to use the controller by the name of vm (ng-controller="TransactionController as vm"). At the bottom of the page you can see some custom scripts named app.js, transaction.controller.js , transaction.service.js. Let’s create them. Add a folder named app under wwwroot and add a JavaScript file named app.js. Again add another folder inside the app folder named transaction and add two more JavaScript files (transaction.controller.js, transaction.service.js). The JavaScript is shown below:

app.js


(function () {
    'use strict';

    angular.module('app', []);
})();

transaction.controller.js


(function () {
    'use strict';

    angular
        .module('app')
        .controller('TransactionController', TransactionController);

    TransactionController.$inject = ['TransactionService'];

    function TransactionController(TransactionService) {
        /* jshint validthis:true */
        var vm = this;

        vm.transactions = [];
        vm.transactionAmount = undefined;

        vm.getTransactions = getTransactions;
        vm.debit = debit;
        vm.credit = credit;

        function getTransactions() {
            TransactionService.getTransactions().success(function (response) {
                vm.transactions = response;
            });
        }

        function debit() {
            var transaction = {
                transactionAmount: vm.transactionAmount,
                transactionType: 'DEBIT'
            }

            TransactionService.addTransaction(transaction).success(function (response) {
                vm.getTransactions();
            });
        }

        function credit() {
            var transaction = {
                transactionAmount: vm.transactionAmount,
                transactionType: 'CREDIT'
            }

            TransactionService.addTransaction(transaction).success(function (response) {
                vm.getTransactions();
            });
        }

        activate();

        function activate() {
            vm.getTransactions();
        }
    }
})();

transaction.service.js


(function () {
    'use strict';

    angular
        .module('app')
        .service('TransactionService', TransactionService);

    TransactionService.$inject = ['$http'];

    function TransactionService($http) {
        this.getTransactions = getTransactions;
        this.addTransaction = addTransaction;

        function getTransactions() {
            return $http({
                method: 'GET',
                url: '/api/transaction'
            });
        }

        function addTransaction(transaction) {
            return $http({
                method: 'POST',
                url: '/api/transaction',
                data: transaction
            });
        }
    }
})();

Notice that we don’t have other library dependencies in our main module (empty []). In TransactionService, we have our GET and POST method. Later, when we will work with the Web API backend we will map those request to appropriate actions. We’ve injected the TransactionService into our TransactionController and used them where it is needed.

Building the backend

In Startup.cs file add the following code,


using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Angular.Asp.Net.Core.XSRF.RC2.Models;
using Angular.Asp.Net.Core.XSRF.RC2.Repository;
using Microsoft.AspNetCore.Antiforgery;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;

namespace Angular.Asp.Net.Core.XSRF.RC2
{
    public class Startup
    {
        // This method gets called by the runtime. Use this method to add services to the container.
        // For more information on how to configure your application, visit http://go.microsoft.com/fwlink/?LinkID=398940
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddAntiforgery(options => options.HeaderName = "X-XSRF-TOKEN");
            services.AddMvc();
            services.AddSingleton();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IAntiforgery antiforgery)
        {
            app.Use(next => context =>
            {
                if (string.Equals(context.Request.Path.Value, "/", StringComparison.OrdinalIgnoreCase) ||
                    string.Equals(context.Request.Path.Value, "/index.html", StringComparison.OrdinalIgnoreCase))
                {
                    var tokens = antiforgery.GetAndStoreTokens(context);
                    context.Response.Cookies.Append("XSRF-TOKEN", tokens.RequestToken,
                        new CookieOptions() {HttpOnly = false});
                }

                return next(context);
            });


            app.UseDefaultFiles();

            app.UseStaticFiles();

            app.UseMvc();

        }

    }
}

We are going to serve the index.html file we’ve created earlier. To serve index.html just add the app.UseDefaultFiles(); just before app.UseStaticFiles();. Note that app.UseDefaultFiles() will only work if your file name is one of the followings,

  • default.htm
  • default.html
  • index.htm
  • index.html

We have added the antiforgery middleware in our ConfigureServices method.


services.AddAntiforgery(options => options.HeaderName = "X-XSRF-TOKEN");

Notice that we have to configure the antiforgery middleware first. There we have added a HeaderName = "X-XSRF-TOKEN" option. This is because if we send a XSRF-TOKEN wrapped in cookie along with a request, angular will send back the same token in a header named X-XSRF-TOKEN when a POST happens. These two tokens must match to verify the request is coming from the same user as it should be.

We must generate and send the antiforgery token when a request is made to either on the http:/app-name/ or http://app-name/index.html url. So, we’ve worked with the current context to do so in the Configure method.

We will create a TransactionRepository which will implement the ITransactionRepository interface. The Interface is for the dependency injection purpose. If you notice in the Startup.cs file, you will see that I'm creating a singleton object of ITransactionRepository and hooked it to the TransactionRepository

Here is the code for ITransactionRepository


using System.Collections.Generic;
using Angular.Asp.Net.Core.XSRF.RC2.Models;

namespace Angular.Asp.Net.Core.XSRF.RC2.Repository
{
    public interface ITransactionRepository
    {
        IEnumerable<Transaction> GetTransactions();
        void AddTransaction(Transaction transaction);
    }
}

And here is the code for TransactionRepository


using System.Collections.Generic;
using Angular.Asp.Net.Core.XSRF.RC2.Models;

namespace Angular.Asp.Net.Core.XSRF.RC2.Repository
{
    public class TransactionRepository : ITransactionRepository
    {
        static readonly List<Transaction> Transactions = new List<Transaction>();
        static readonly Account Account = new Account() { AccountNumber = "1234", CurrentBalance = 1000};

        public IEnumerable<Transaction> GetTransactions()
        {
            return Transactions;
        }

        public void AddTransaction(Transaction transaction)
        {
            if (transaction.TransactionType.Equals("DEBIT"))
            {
                Account.CurrentBalance = Account.CurrentBalance - transaction.TransactionAmount;
                transaction.Account = new Account { AccountNumber= Account.AccountNumber, CurrentBalance = Account.CurrentBalance};

            }
            else
            {
                Account.CurrentBalance = Account.CurrentBalance + transaction.TransactionAmount;
                transaction.Account = new Account { AccountNumber = Account.AccountNumber, CurrentBalance = Account.CurrentBalance };
            }

            Transactions.Add(transaction);
        }
    }

In GetTransaction method we returned all the transactions that were made and in the AddTransaction method we’ve implement some conditions to make a debit and credit transaction against an account created in the constructor.

Here are the two class entity (Account, Transaction) definitions we’ve used in the repository. Make a folder named Models and put these .cs files with the following code,

Transaction.cs


namespace Angular.Net.Core.XSRF.Models
{
    public class Transaction
    {
        public decimal TransactionAmount { get; set; }
        public string TransactionType { get; set; }
        public Account Account { get; set; }
    }
}

Account.cs


namespace Angular.Net.Core.XSRF.Models
{
    public class Account
    {
        public string AccountNumber { get; set; }
        public decimal CurrentBalance { get; set; }
    }
}

Now let’s add an API controller for exposing this two functionalities (AddTransaction, GetTransactions) over HTTP. Add a folder named controller and add a Web API Controller named TransactionController.

Add the following code in the TransactionController


using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Angular.Net.Core.XSRF.Models;
using Angular.Net.Core.XSRF.Repository;
using Microsoft.AspNetCore.Mvc;

// For more information on enabling Web API for empty projects, visit http://go.microsoft.com/fwlink/?LinkID=397860

namespace Angular.Net.Core.XSRF.Controllers
{
    [Route("api/[controller]")]
    public class TransactionController : Controller
    {
        public ITransactionRepository Repository;

        public TransactionController(ITransactionRepository repository)
        {
            Repository = repository;
        }
        [HttpGet]
        public IEnumerable Get()
        {
            return Repository.GetTransactions();
        }

        [HttpPost]
        [ValidateAntiForgeryToken]
        public void Post([FromBody]Transaction transaction)
        {
            Repository.AddTransaction(transaction);
        }
    }
}

We are almost at the end. We need to tell our Web API to talk with our client side app in json language. I mean we have to do some content negotiation. That’s easy, just chain another method with the AddMvc() extension method, here is how it looks like,


services.AddMvc().AddJsonOptions(options => SerializerSettings.ContractResolver = new ePropertyNamesContractResolver());

By the way CamelCasePropertyNamesContractResolver is available in another third party .net libray called json.net. So open up the Manage NuGet Packages window and add it in your project

Now build and run the app. Going to the root url the application will generate a XSRF-TOKEN and store it as a cookie. Further when a POST request is made the same token value will be passed a header named X-XSRF-TOKEN. Here the other one is the application cookie. Now if you try to mimic the POST request and try making it from another domain or application, that simply won’t work. Because even if you manage to use the application cookie your XSRF-TOKEN will not match.

Download Source Code

Download the source code from github. Dont forget to give it a star

https://github.com/fiyazbinhasan/asp.net-core-rc2-xsrf-angular