Segurança é um dos pilares em grande parte das aplicações de Software hoje em dia, e assim como o ASP NET Core trouxe diversas novidades em caching, injeção de dependência e outros recursos, o modelo de segurança também foi remodelado, e claro, para melhor. Neste artigo vou falar um pouco sobre o ASP NET Core Identity e sobre o novo modelo de autorização (Sim, eles podem ser utilizados de forma independente. Podemos optar em utilizar somente o modelo simples de autenticação via Cookies ao invés de integra-lo com o Identity), e mostrar suas principais funcionalidades e inovações disponibilizadas pelo time do ASP NET Core.

Visão Geral

O ASP.NET Core Identity é um recurso que permite aos desenvolvedores implementarem requisitos de autenticação e autorização em suas aplicações. Estes termos podem ser bastante familiares para quem já trabalhou com as versões anteriores do modelo, mas nesta nova versão as guias de implementação foram completamente reformuladas para permitir maior flexibilidade na implementação dos mais específicos requisitos de segurança.

Assim como nas versões anteriores você pode configurá-lo para persistir os logins, perfis de usuário e senhas  em uma base de dados SQL Server, ou pode optar em utilizar outro mecanismo de persistência, como por exemplo, o Azure Table Storage. Não entrarei em detalhes de persistência pois o objetivo deste artigo é exemplificar as vantagens e flexibilidade do modelo na implementação de critérios de segurança.

Autenticação

Vamos começar o artigo implementando autenticação, e depois quando tudo estiver configurado partiremos para autorização. Caso você não esteja familiarizado com as diferenças entre estes dois termos, segue abaixo uma breve descrição de ambos extraído da Microsoft TechNet:

  • A autenticação consiste na verificação das credenciais da tentativa de ligação. Este processo consiste no envio de credenciais do cliente de acesso remoto para o servidor de acesso remoto em formato de texto simples ou encriptado, através da utilização de um protocolo de autenticação.
  • A autorização consiste na verificação da permissão da tentativa de ligação. A autorização ocorre após uma autenticação com êxito.

Para resumir, a autenticação está relacionada a Identificação e autorização está relacionada a Permissões, portanto, implementaremos primeiramente a autenticação para que possamos saber quem está utilizando a aplicação e depois implementaremos a autorização para verificar se o usuário possui privilégios suficientes para acessar determinada funcionalidade ou recurso do sistema.

Atenção: Utilizaremos o template de aplicação Web disponibilizado dentro do Visual Studio por padrão a fim de ilustrar o artigo, mas você pode configurar o Identity em um template vazio ou em uma aplicação ASP NET 5 existente conforme necessário.

Abra o Visual Studio 2015 e selecione a opção de criar um novo projeto. Siga os passos abaixo:

Em Visual C# -> Web, selecione a opção "ASP.NET Web Application".

Em Visual C# -> Web, selecione a opção “ASP.NET Web Application”.

Selecione o template "Web Application" localizado na seção "ASP NET 5 Templates".

Selecione o template “Web Application” localizado na seção “ASP NET 5 Templates”.

Antes de clicar em “OK” para criar o novo projeto a partir de um template ASP.NET 5, certifique-se que o modelo de autenticação selecionado está conforme a imagem a seguir. Para abrir esta janela, basta clicar em “Change Authentication”:

Modelo de Autenticação.

Modelo de Autenticação.

Agora já temos uma aplicação ASP NET 5 com o modelo de segurança do Core Identity configurado nela. A seguir, explicarei cada parte que compõe o todo da configuração do serviço e do modelo de autenticação, assim como os métodos responsáveis por criar os usuários. Vamos primeiramente inspecionar o arquivo “Startup.cs” na raiz da Solution. É nele que injetamos e configuramos o novo modelo de identidade.

Injeção do Serviço de Identity no pipeline da aplicação.

Injeção do Serviço de Identity no pipeline da aplicação.

Com o arquivo “Startup.cs” aberto, podemos visualizar o método “ConfigureServices”. Este método é responsável por injetar os serviços que serão utilizados no pipeline do ASP NET. Em destaque podemos ver o código que realiza a configuração do serviço de Identity, e perceba que estamos utilizando o mesmo contexto do Entity Framework que foi configurado logo acima. O método “AddIdentity” configura dois tipos genéricos que são (Perceba o quão flexível é o novo modelo disponibilizado no ASP NET 5):

  • TUser: O tipo da classe que armazenará informações do usuário. Neste exemplo está sendo utilizando o tipo disponibilizado pelo Identity com a classe “ApplicationUser” que atende a maioria dos cenários.
  • TRole: O tipo da classe que armazenará as informações de um grupo de permissão. Neste exemplo está sendo utilizado o tipo disponibilizado pelo Identity com a classe “IdentityRole” que atende a maioria dos cenários.
Habilitando o Identity.

Habilitando o Identity.

O método “Configure” é chamado logo após o fim da instrução “ConfigureServices”, e nele habilitamos o serviço que foi injetado anteriormente conforme seção destacada na imagem acima.

Se executarmos a aplicação, ao ser aberta no browser padrão podemos ver algumas opções no menu como “Register” ou “Login”. Estes recursos da aplicação utilizam o Identity que foi configurado no projeto, e a seguir vamos falar sobre as duas principais classes de autenticação do Identity que podemos injetar nos nossos Controllers.

Se abrirmos o código da classe “AccountController” localizada dentro da pasta “Controllers”, podemos visualizar o seguinte construtor:

Construtor da classe AccountsController.

Construtor da classe “AccountsController”.

No construtor são injetadas duas classes com o tipo genérico configurado como ApplicationUser. Estas são as principais classes utilizadas :

  • UserManager<T>: Esta classe gerencia os usuários da aplicação. Confira alguns dos métodos disponíveis:
    • CreateAsync: Cria um novo usuário para a aplicação;
    • DeleteAsync: Deleta um usuário existente;
    • AddClaimAsync: Adiciona uma claim para um usuário. Uma claim é uma porção de informação que compõe os dados de usuário. Alguns exemplos de Claims: Email, endereço, idade, Administrador, etc;
    • RemoveClaimAsync: Remove uma claim de um usuário;
    • FindByEmailAsync: Encontra um usuário a partir de seu Email;
    • GeneratePasswordResetTokenAsync: Gera um Token de alteração de senha que pode ser enviado para um endereço de Email caso o usuário tenha esquecido sua senha;
    • ResetPasswordAsync: Reconfigura uma senha a partir de um Token gerado previamente.
  • SignInManager<T>: Esta classe gerencia as sessões dos usuários. Confira alguns dos métodos disponíveis:
    • SignInAsync: Cria uma sessão ativa para um usuário. Este método é útil caso você tenha acabado de criar um novo usuário e queira autentica-lo no sistema com o registro de usuário previamente criado;
    • PasswordSignInAsync: Autentica um usuário com seu Email e senha. Este método é útil ao receber dados de um formulário de Login;
    • SignOutAsync: Remove a sessão ativa de um usuário (Não são necessários parâmetros).
  • ApplicationUser: Esta é a classe que contém as informações de um usuário (Configuramos ela como nosso TUser ao injetarmos o Identity no pipeline, lembra?). Nela podemos configurar e acessar diversas informações como o Email do usuário, telefone, grupos a quais está ingressado e suas respectivas Claims.

As classes acima possuem diversos outros métodos. Eu apenas descrevi alguns dos principais, mas caso se interesse em conhecer mais a API, verifique a documentação oficial do ASP NET Core. Com estas duas classes podemos controlar a autenticação de usuários em nossa aplicação bem como gerenciar estes respectivos usuários. Agora que já falamos sobre autenticação, vamos prosseguir para o próximo tópico onde falaremos sobre autorização.

Autorização

Agora que revisamos o modelo de autenticação, podemos prosseguir e falar um pouco sobre autorização.

Antes de mais nada é necessário adicionar ao nosso projeto o seguinte pacote (Você pode adicionar a referência via NuGet ou diretamente no arquivo “project.json”):

Microsoft.AspNet.Authorization

Nas versões anteriores do ASP NET era muito comum a utilização de autorização por Roles (Grupos). Este modelo de autorização ainda é suportado e podemos utilizá-lo decorando um Controller ou um método (MVC/WebAPI) da seguinte maneira:

[Authorize(Roles = "Administrador")]

Adicione o atributo acima no método “About” do “HomeController” e experimente executar a aplicação. Certamente você será redirecionado para uma URL parecida com esta “http://localhost:43462/Account/AccessDenied”.

Para se certificar que um usuário participa de um grupo basta adicionar a seguinte Claim na classe “AccountController” (Logo abaixo de onde está sendo testado se o usuário foi criado com sucesso):

await _userManager.AddClaimAsync(user, new Claim(ClaimTypes.Role, "Administrador", ClaimValueTypes.String));

O código acima é suficiente para utilizar Role Based Authorization em sua aplicação, entretanto acredito que a maioria dos desenvolvedores já está bem familiarizada com este método. Vamos prosseguir e falar dos novos recursos.

Politicas de Autorização

Uma das novidades é o modelo de autorização baseado em Policies. Ele é bastante similar ao modelo de Roles, mas adiciona um pouco mais de flexibilidade. Suponhamos que haja um requisito onde somente usuários com os emails “[email protected]” e “[email protected].com.br” possam acessar determinado recurso. Poderíamos implementar uma politica da seguinte maneira:

Adicione a um método ou Controller o seguinte atributo:

[Authorize(Policy = "EmailsSuperUsers")]

Adicione o seguinte código ao método “ConfigureServices” da classe “Startup” logo abaixo da configuração do Identity:

services.AddAuthorization(options =>
{
	options.AddPolicy("EmailsSuperUsers", policy => policy.RequireClaim("Email", "[email protected]", "[email protected]"));
});

E caso queira testar a execução deste código é necessário adicionar mais uma Claim ao nosso usuário no nosso método “Register” com a seguinte linha de código:

await _userManager.AddClaimAsync(user, new Claim("EmailsSuperUsers", "[email protected]", ClaimValueTypes.Email));

Atenção: Esta não é uma maneira eficiente de utilizar as policies. Em uma aplicação real utilizaríamos valores persistidos/gerenciados por algum mecanismo de armazenamento de dados. Esta ilustração apenas exemplifica que podemos utilizar validação em policies através de valores existentes em Claims de usuários. Utilizar múltiplas chamadas de AddClaimAsync não é uma maneira eficiente de gerenciar Claims. Caso seja necessário adicionar um conjunto de Claims, utilize o método “AddClaimsAsync” que recebe uma coleção de Claims.

Politicas de Autorização baseadas em Código

Em alguns cenários é necessário realizar alguma validação baseada em código ou ate mesmo verificar alguns dados em algum mecanismo de persistência (Muito cuidado ao acessar mecanismos de armazenamento de dados pois isto pode adicionar um overhead considerável na sua aplicação). Para estes cenários podemos implementar  a interface IAuthorizationRequirement e a classe abstrata AuthorizationHandler<T>. Suponhamos que haja em nossa aplicação um requisito onde é necessário validar se o usuário possui Email associado ao domínio “netcoders.com.br”. Poderíamos implementar este requisito da seguinte maneira (Não e necessário configurar a nossa Claim de Email porque ela já foi configurada no tópico anterior):

using System;
using System.Security.Claims;
using Microsoft.AspNet.Authorization;

namespace IdentityDemo
{
    public class PossuiEmailNetCodersRequirement : AuthorizationHandler<PossuiEmailNetCodersRequirement>, IAuthorizationRequirement
    {
        protected override void Handle(AuthorizationContext context, PossuiEmailNetCodersRequirement requirement)
        {
            // Verifica se o usuário de contexto possui uma Claim do tipo EMAIL em sua coleção de Claims.
            if (context.User.HasClaim(c => c.Type == ClaimTypes.Email))
            {
                // Obtendo Email a partir da Claim do usuário em contexto.
                var email = context.User.FindFirst(c => c.Type == ClaimTypes.Email).Value;

                // Validamos o Email aqui para averiguar se possui o domínio do netcoders.
                if (email.Contains("@netcoders.com.br"))
                {
                    context.Succeed(requirement);
                }
            }

            // Quebrando o fluxo do pipeline de autenticação. Se este código for executado o usuário receberá um erro HTTP "Unauthorized"
            return;
        }
    }
}

Após implementar esta classe, basta apenas certificar que a politica está registrada com o seguinte código dentro da classe “Startup.cs”:

services.AddAuthorization(options =>
{
	options.AddPolicy("PossuiEmailNetCoders", policy => policy.Requirements.Add(new PossuiEmailNetCodersRequirement()));
}

Pronto! A partir deste momento podemos utilizar a nova politica em qualquer Controller decorando-os com o atributo:

Authorize[Policy="PossuiEmailNetCoders"]

Caso você esteja se perguntando “Mas como eu posso acessar algum mecanismo de armazenamento de dados, Caching ou outra dependência a partir deste AuthorizationHandler?” A resposta é simples! O novo modelo do ASP NET (Core) possui um container de injeção de dependência nativo, então para isto basta injetarmos as dependências via IoC. A classe AuthorizationHandler também esta completamente integrada com o modelo de injeção de dependência.

Conclusão

A partir deste artigo podemos perceber que a Microsoft acertou em cheio com o novo modelo de autenticação e autorização e trouxe bastante flexibilidade para os desenvolvedores implementarem requisitos de segurança em suas aplicações ASP NET Core. O modelo possui diversos outros recursos mais avançados que eu optei por não discutir sobre pois o artigo ficaria muito extenso, mas apenas como nota saiba que neste modelo ainda é possível:

  • Agregar mais de um AuthorizationHandler por requisito de segurança (Em casos onde você pode aprovar a autorização de um usuário por N maneiras);
  • Autorização a nível de recurso injetando a interface IAuthorizationService e validando a nível de fluxo de código;
  • Autorização a nível de views MVC injetando a interface IAuthorizationService diretamente no Razor.

Um grande abraço e até a próxima pessoal!

Referências

https://docs.asp.net/en/latest/security/authentication/introduction-to-aspnet-identity.html

https://technet.microsoft.com/pt-pt/library/cc759647(v=WS.10).aspx

https://github.com/blowdart/AspNetAuthorizationWorkshop

Desenvolvedor .NET há mais de 5 anos, apaixonado por Arquitetura e Desenvolvimento de Software.

Atuo hoje como Senior Software Developer em [email protected] Zelândia, mas ao longo da minha carreira profissional trabalhei como Analista de Sistemas e Desenvolvedor em algumas empresas brasileiras e em vários projetos de Software!

Microsoft Certified Professional
MS: Programming in HTML5 with JavaScript and CSS3
MCTS: Web Applications Development with Microsoft .NET Framework 4

Website: http://rafaelpc.com/
LinkedIn: https://br.linkedin.com/in/rafaelpaschoal

Comentários

comentarios