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.
Muito bom! Obrigado pela a orientação
Muito obrigado pela visita
Abs.,