Validando CSRF Token em chamada Ajax com MVC

28 jan

Hoje abordarei um tema delicado em aplicações web modernas, a segurança. É comum em aplicações web utilizar chamadas AJAX para aumentar a usabilidade, a fluidez e o jeito de o usuário interagir com a aplicação, usamos diversos Framework de MVVM como Angular JS, Ember, Backbone, ReactJS para que  facilite a nossa vida ao criar aplicações web altamente inovadores e responsivas.

Alias estou preparando um post sobre as novidades do AngularJS 2.0 e um outro post sobre AngularJS x ReactJS.

Mas voltando o assunto, o ASP.NET MVC contém uma segurança para envio de formulário chamada @Html.AntiForgeryToken(). O principal papel dele é prevenir comandos não autorizados no envio dos dados de formulários para o servidor processar. Um exemplo clássico de ataque em aplicações web é o CROSS-SITE SCRIPTING (XSS)

Um erro muito comum de nós desenvolvedores é em uma chamada AJAX esquecermos de passar o Token CSRF ou simplesmente ignorar ele, deixando nossas aplicações vulneráveis a ataque.

Nós desenvolvedores devemos ter a certeza que todas as informações enviadas para o servidor processar não foram alteradas de forma maliciosa. Sendo assim como podemos resolver esse problema?

Eu escolhi o AngularJS para fazer a parte do client side porém fique a vontade e escolha o MVVM Client Side de sua preferência

Nesse exemplo criei um formulário simples de cadastro de cliente no qual a comunicação será via AJAX.

Client Side:

Aqui vamos desenvolver a parte client side no qual será responsável de obter a informações do Token e do Cookie gerado pelo ASP.NET MVC .

<!DOCTYPE html>
<html ng-app="app">
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" />
    <script src="//code.jquery.com/jquery-1.12.0.min.js"></script>
    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js"></script>
    <script src="https://code.angularjs.org/1.4.9/angular.min.js"></script>
    
</head>
<body ng-controller="ClientController">
    <div class="navbar navbar-inverse navbar-fixed-top">
        <div class="container">
            <div class="navbar-header">
                <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                </button>
            </div>

        </div>
    </div>
    
    <div class="container body-content" style="margin-top:100px;">
        
        <form name="form" ng-submit="createClient();" novalidate>
            @Html.AntiForgeryToken();
            <div class="form-group" ng-class="{ 'has-error': form.nome.$invalid && !form.nome.$pristine }">
                <label for="exampleInputEmail1">Nome Completo</label>
                <input type="text" required ng-model="name" class="form-control" name="nome" placeholder="Nome Completo">
            </div>
            <div class="form-group" ng-class="{ 'has-error': form.cpf.$invalid && !form.cpf.$pristine }">
                <label for="exampleInputEmail1">CPF</label>
                <input type="number" ng-required ng-model="cpf" class="form-control" name="cpf" placeholder="CPF">
            </div>
            <div class="form-group" ng-class="{ 'has-error': form.email.$invalid && !form.email.$pristine }">
                <label for="exampleInputPassword1">Email</label>
                <input type="email" ng-required ng-model="email" class="form-control" name="email" placeholder="Email">
            </div>
            <div class="form-group" ng-class="{ 'has-error': form.senha.$invalid && !form.senha.$pristine }">
                <label for="exampleInputPassword1">Senha</label>
                <input type="password" ng-required ng-model="password" class="form-control" name="senha" placeholder="Password">
            </div>

            <button class="btn btn-default btn-primary" ng-disabled="form.$invalid">Submit</button>
        </form>
        <hr />
    </div>
    <script>
        angular.module("app", [])
            .controller("ClientController",["$scope", "$http", function ($scope, $http) {
                $scope.createClient = function () {
                   
                    if ($scope.form.$invalid)
                        return;

                    var data = {
                        name: $scope.name,
                        cpf: $scope.cpf,
                        email: $scope.email,
                        senha: $scope.password
                    }

                    $http.post('/home/createuser', data).then(function (response) {

                    });
                };
        }]);
    </script>
</body>
</html>

 

Criamos nosso código client side básico, nele colocamos o AngularJs, fizemos a validação do formulário e inserimos o CSRF Token. Ainda precisamos desenvolver mais algumas funcionalidades.

Precisamos fazer o AngularJS enviar o CSRF Token para o nosso servidor e verificar se ele é um token válido. Temos duas maneiras de fazer isso. A primeira é enviar usando o objeto $http e a segunda é configurar o $httpProvider para sempre tentar ler o token caso ele exista. Vamos implementar as duas formas de envio.

Back End

Agora que temos temos nosso client side quase pronto, precisamos desenvolver a Action que irá processar a chamada AJAX

public class HomeController : Controller
{
       public ActionResult Index()
       {
           return View();
       }

       public ActionResult CreateUser(string name, string cpf, string email, string senha)
       {
           Cliente cliente = new Cliente()
           {
               name = name,
               cpf = cpf,
               email = email,
               senha = senha
           };

           return Json("OK");

       }

       public class Cliente
       {
           public string name { get; set; }
           public string cpf { get; set; }
           public string email { get; set; }
           public string senha { get; set; }
       }
}

Criada nossa Action, iremos utilizar os Filters do ASP.NET MVC para validar o token. Esse filtro deverá verificar a existência o CSRF Token e se ele está válido.

public class AntiForgeryTokenForAjaxAttribute : FilterAttribute, IAuthorizationFilter
   {
       public void OnAuthorization(AuthorizationContext filterContext)
       {
           if (filterContext == null)
           {
               throw new ArgumentNullException("filterContext");
           }

           var httpContext = filterContext.HttpContext;
           var cookie = httpContext.Request.Cookies[AntiForgeryConfig.CookieName];
           AntiForgery.Validate(cookie != null ? cookie.Value : null, httpContext.Request.Headers["X-CSRFToken"]);
       }

   }

No código acima verificamos a existência do CSRF Token e assim validamos a sua autenticidade.

Agora basta usar o filtro na nossa Action

[AntiForgeryTokenForAjax]
public ActionResult CreateUser(string name, string cpf, string email, string senha)
{
   Cliente cliente = new Cliente()
   {
       name = name,
       cpf = cpf,
       email = email,
       senha = senha
   };
   return Json("OK");

}

 

Voltando ao client side:

Agora que nosso back end está pronto, devemos alterar nosso client side para enviar no header http o CSRF Token. Como mencionado acima temos duas formas de fazer o envio.

Vamos ao código:

angular.module("app", [])
          .controller("ClientController",["$scope", "$http", function ($scope, $http) {
              $scope.createClient = function () {
                 
                  if ($scope.form.$invalid)
                      return;

                  var data = {
                      name: $scope.name,
                      cpf: $scope.cpf,
                      email: $scope.email,
                      senha: $scope.password
                  }

                  var token = $("input[name='__RequestVerificationToken']").length > 0 ? $("input[name='__RequestVerificationToken']").val() : ""

                  $http({
                      url: "/home/createuser",
                      method: 'POST',
                      headers: {
                          'X-CSRFToken': token
                      },
                      data:data
                  }).then(function (response) {
                      alert("cliente cadastrado com sucesso");
                  })
              };
      }]);

 

A segunda forma é configurar o $httpProvider para sempre enviar o token de forma automática. Para isso vamos utilizar o module config do AngularJS para configurar o header padrão.

angular.module("app", [])
           .controller("ClientController",["$scope", "$http", function ($scope, $http) {
               $scope.createClient = function () {
                  
                   if ($scope.form.$invalid)
                       return;

                   var data = {
                       name: $scope.name,
                       cpf: $scope.cpf,
                       email: $scope.email,
                       senha: $scope.password
                   }

                   $http.post('/home/createuser', data).then(function (response) {
                       alert("cliente cadastrado com sucesso");
                   })
               };
           }]).config(['$httpProvider', function ($httpProvider) {
               var token = $("[name='__RequestVerificationToken']").val();
               $httpProvider.defaults.headers.common['X-CSRFToken'] = token;
           }]);

Pronto, agora toda vez que algum desenvolvedor fizer uma chamada AJAX iremos injetar o CSRF Token no cabeçalho Http facilitando a vida de todo nós meros desenvolvedores.

Então chegamos ao final do post, espero gostem o que foi abordado e não deixem de comentar como dizem, sua opinião é muito importante para nós =]

Abs e até a próxima.

2 Replies to “Validando CSRF Token em chamada Ajax com MVC

Deixe uma resposta

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *

Esse site utiliza o Akismet para reduzir spam. Aprenda como seus dados de comentários são processados.