ASP .NET Core 3.1 - Autenticação com cookies


  Neste artigo vamos abordar a autenticação via Cookies na ASP .NET Core 3.1.

A ASP.NET Core fornece um middleware de cookies, que serializa um usuário principal em um cookie criptografado e, em requisições subsequentes, valida o cookie, recria o seu proprietário e o atribui à propriedade User em HttpContext.

Assim, se você deseja fornecer suas próprias telas de login e bancos de dados de usuários, pode usar o middleware de cookies como um recurso independente.

O middleware de autenticação intercepta requisições recebidas e verifica a existência de um cookie contendo dados do usuário criptografados.

Se um cookie for encontrado, ele será serializado em um tipo de ClaimPincipal e poderá ser acessado através da propriedade HttpContext.User.

Se um cookie não for encontrado, o middleware será redirecionado para a página de login usando um método Action. Por meio da página de login, você recebe detalhes do usuário e se autentica nos registros do banco de dados. Depois de autenticado, você vai precisar :

Para isso vamos usar os conceitos relacionados aos seguintes recursos:

  1. [Authorize] :- atributo que ajuda a validar o usuário e permitir o acesso a uma Action ou um Controller;
  2. Claim :- Contém a informação do usuário a qual será armazenada no cookie;
  3. ClaimsIdentity :- Passa a lista de declarações (claims) e AuthenticationTypes;
  4. ClaimsPrincipal :-  Aceita um array de ClaimsIdentity;
  5. SignInAsync:- Passa a ClaimsPrincipal como parâmetro e cria o cookie no navegador;

Opções de Cookies

As opções de cookie informam ao middleware de autenticação como o cookie se comporta no navegador. Existem muitas opções, a seguir temos apenas naquelas que afetam mais a segurança dos cookies.

Definir HttpOnly como false no cookie de autenticação substituirá as opções de política de cookies , e, ao definir o SameSite na política de cookies, as opções de cookies de autenticação são substituídas.

Neste exemplo vamos implementar a autentica via cookies sem usar o Identity.

Recursos :

Criando o projeto no VS 2019 Community

Abra o VS 2019 Community e crie uma solução em branco via menu File-> New Project;

Selecione o template ASP .NET Core Web Application, e, Informe o nome Aspnet_AuthCookies1;

A seguir selecione .NET Core e ASP .NET Core 3.1 e marque o template Web Application e as configurações conforme figura abaixo:

Podemos executar a aplicação pressionando F5 e veremos a página padrão do template exibida no navegador.

Integrando a autenticação com cookies

Como vamos implementar a autenticação via cookies sem usar o Identity precisamos configurar o middleware de cookies no método ConfigureServices da classe Startup:

...
public void ConfigureServices(IServiceCollection services)
{

   services.AddAuthentication("CookieAuthentication")
       .AddCookie("CookieAuthentication", config =>
       {
          config.Cookie.Name = "UserLoginCookie";
          config.LoginPath = "/Login/UserLogin";
          config.AccessDeniedPath = "/Login/AccessDenied";
       });


       services.AddControllersWithViews();
}
...

Neste código iniciamos o middleware de cookies e definimos o nome do cookie como UserLoginCookie que será criado, e, também definimos :

- LoginPath - Caminho relativo para o qual as requisições serão redirecionadas quando um usuário tentar acessar um recurso, mas não tiver sido autenticado;

- AccessDeniedPath - (opcional) Caminho relativo para o qual as requisições serão redirecionadas quando um usuário tentar acessar um recurso, mas não passar nenhuma política de autorização para esse recurso;

A seguir precisamos incluir os métodos de extensão UseAuthentication e UseAuthorization ao método Configure da classe Startup :

No método Configure inclua a chamada a esses middlewares na ordem indicada:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
     ...
     ...
    // Quem é você?

    app.UseAuthentication();

    // Verifica Permissões
    app.UseAuthorization();

    app.UseEndpoints(endpoints =>
    {
           endpoints.MapControllerRoute(
           name: "default",
           pattern: "{controller=Home}/{action=Index}/{id?}");
    });
}

A seguir vamos criar um modelo de domínio para identificar o usuário criando a classe Usuario e definindo um método GetUsuarios onde vamos atribuir valores aos dados de um usuário para efeito de teste.

Na pasta Models crie o arquivo Usuario.cs com o código abaixo:

public class Usuario
{
   public int Id { get; set; }
   public string Nome { get; set; }
   public string Login { get; set; }
   public string Email { get; set; }
   public string Password { get; set; }

   public IEnumerable<Usuario> GetUsuarios()
   {
        return new List<Usuario>() {
              new Usuario { Id = 101, Nome = "Jose Carlos",
                                 Login = "macoratti", Email = "macoratti@teste.com",
                                 Password = "#numsey@" }
                               };
       }
}

Ajustando o controlador HomeController e as suas views

Vamos alterar o código do controlador HomeController criado na pasta Controllers e vamos definir dois métodos Action:

  1. Index - Verifica se o usuário esta autenticado e vai apresentar a view inicial ;
  2. Usuarios - Obtém os dados do usuário definido no model Usuario via método GetUsuarios();
using Aspnet_AuthCookies1.Models;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;

namespace Aspnet_AuthCookies1.Controllers
{
  public class HomeController : Controller
  {
     public IActionResult Index()
     {
         var usuario = "Anônimo";
         var autenticado = false;
         if (HttpContext.User.Identity.IsAuthenticated)
         {
             usuario= HttpContext.User.Identity.Name;
             autenticado = true;

         }
         else
         {
            usuario = "Não Logado";
            autenticado = false;
         }

         ViewBag.usuario = usuario;
         ViewBag.autenticado = autenticado;

         return View();
    }

    [Authorize]
    public ActionResult Usuarios()
    {
      var usuario = new Usuario();
      return View(usuario.GetUsuarios());
    }
  }
}

Observe que o método Action Usuarios() esta decorado com o atributo [Authorize] que vai permitir acesso somente aos usuários autenticados.

Agora vamos ajustar o código da view Index.cshtml da pasta /Views/Home conforme abaixo:

@ViewBag.usuario
<div>
    @if (@ViewBag.autenticado == true)
    {
        <form method="post" asp-controller="Login" asp-action="Logout">
            <input class="btn btn-link" type="submit" value="Logout" />
        </form>
    }
</div>
<hr />
<div class="jumbotron">
    <h1 class="display-4">Macoratti .net</h1>
</div>

Este código exibie o status do usuário e verifica se o mesmo esta autenticado. Se estive então exibie o link para fazer o logout acionando o controlador Login e o método Action Logout.

Agora vamos criar a view Usuarios e para isso clique com o botão do mouse sobre o método Action Usuarios e a seguir selecione Add View;

Depois informa o nome da view como Usuarios e escolha o template Empty e marque para usar a página de leiaute conforme mostra a figura abaixo:

Agora inclua o código abaixo na view Usuarios.cshtml da pasta /Views/Home :

@model IEnumerable<Aspnet_AuthCookies1.Models.Usuario>
@{
    ViewData["Title"] = "Index";
}
<h1>Usuários</h1>
<table class="table">
    <thead>
        <tr>
            <th>@Html.DisplayNameFor(model => model.Id)</th>
            <th>@Html.DisplayNameFor(model => model.Nome)</th>
            <th>@Html.DisplayNameFor(model => model.Login)</th>
            <th>@Html.DisplayNameFor(model => model.Email)</th>
            <th>@Html.DisplayNameFor(model => model.Password)</th>
            <th></th>
        </tr>
    </thead>
    <tbody>
        @foreach (var item in Model)
        {
            <tr>
                <td>@Html.DisplayFor(modelItem => item.Id)</td>
                <td>@Html.DisplayFor(modelItem => item.Nome)</td>
                <td>@Html.DisplayFor(modelItem => item.Login)</td>
                <td>@Html.DisplayFor(modelItem => item.Email)</td>
                <td>@Html.DisplayFor(modelItem => item.Password)</td>
                <td>
                    @Html.ActionLink("Edita", "Edit", new { /* id=item.PrimaryKey */ }) |
                    @Html.ActionLink("Detalhe", "Details", new { /* id=item.PrimaryKey */ }) |
                    @Html.ActionLink("Deleta", "Delete", new { /* id=item.PrimaryKey */ })
                </td>
            </tr>
        }
    </tbody>
</table>

Este código apenas vai exibir a lista de usuários definidos e somente poderá acessada se o usuário estiver autenticado.

Criando o controlador LoginController

No arquivo appsettings.json vamos incluir o código que define a string de conexão com uma instância do SQL Server local:

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "ConnectionStrings": {
    "sqlConnection": "Data Source=Macoratti;Initial Catalog=CadastroDB;Integrated Security=True"
  },
  "AllowedHosts": "*"
}

Nota : A migração deve ser aplicada apenas se você não possuir o banco de dados e a tabela definidas.

Com isso, já temos tudo pronto e agora podemos aplicar o Migrations para gerar o banco de dados e a tabela.

Abrindo uma janela do Package Manager Console e digitar o comando para criar a migração: Add-Migration Inicial

Será criado o arquivo de script de migração na pasta Migrations :

Para aplicar o script basta digitar : Update-database

Criando o controlador LoginController

Vamos criar o controlador LoginController e definir os seguintes métodos Actions:

  1. UsuarioLogin (GET) - Apresenta a view UsuarioLogin para informação das credenciais do usuário;
  2. UsuarioLogin (POST) - Recebe as informações postadas e verifica as credenciais e define e cria o cookie;
  3. Logout (POST) - Realiza o logout do usuário autenticado;

Clique com o botão direito sobre a pasta Controllers e a seguir clique em Add -> Controller;

Escolha o template MVC Controller Empty e informe o nome LoginController;

A seguir inclua o código abaixo no controller:

using Aspnet_AuthCookies1.Models;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
namespace Aspnet_AuthCookies1.Controllers
{
    public class LoginController : Controller
    {
        [HttpGet]
        public ActionResult UsuarioLogin()
        {
            return View();
        }
        [HttpPost]
        public ActionResult UsuarioLogin([Bind] Usuario _usuario)
        {
            var usuario = new Usuario();
            if (usuario.GetUsuarios().Any(u => u.Login == _usuario.Login && u.Password == _usuario.Password))
            {
                var userClaims = new List<Claim>()
                {
                    //define o cookie
                    new Claim(ClaimTypes.Name, _usuario.Login),
                    new Claim(ClaimTypes.Email, "macoratti@teste.com"),
                };
                var minhaIdentity = new ClaimsIdentity(userClaims, "Usuario");
                var userPrincipal = new ClaimsPrincipal(new[] { minhaIdentity });
                //cria o cookie
                HttpContext.SignInAsync(userPrincipal);
                return RedirectToAction("Index", "Home");
            }
            ViewBag.Message = "Credenciais inválidas...";
            return View(_usuario);
        }
        [HttpPost]
        public async Task<IActionResult> Logout()
        {
            await HttpContext.SignOutAsync();
            return RedirectToAction("Index", "Home");
        }
    }
}

Para criar o formulário de login vamos criar a view UsuarioLogin a partir do método Action UsuarioLogin e incluindo o código abaixo no arquivo UsuarioLogin.cshtml :

@model Aspnet_AuthCookies1.Models.Usuario
@{
    ViewData["Title"] = "Login";
}
<div class="row">
    <div class="col-md-4">
        <form asp-action="UsuarioLogin">
            <h2>User Login</h2>
            <div asp-validation-summary="ModelOnly" class="text-danger"></div>
            <div class="form-group">
                <label asp-for="Login" class="control-label"></label>
                <input asp-for="Login" class="form-control" />
                <span asp-validation-for="Login" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Password" class="control-label"></label>
                <input type="password" asp-for="Password" class="form-control" />
                <span asp-validation-for="Password" class="text-danger"></span>
            </div>
            <div class="form-group">
                <input type="submit" value="Login" class="btn btn-default btn-primary" />
            </div>
        </form>
    </div>
</div>
<br />
<h2>@ViewData["Message"]</h2>

Para concluir vamos ajustar o arquivo _Layout.cshtml da pasta /Views/Shared para exibir o link e acessar os usuários:

...

     <div class="navbar-collapse collapse d-sm-inline-flex flex-sm-row-reverse">
        <ul class="navbar-nav flex-grow-1">
          <li class="nav-item">
            <a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Index">Home</a>
         </li>
        <li class="nav-item">
         <a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Usuarios">Usuarios</a>
        </li>
     </ul>
      </div>
...

Agora é só alegria...

Executando o projeto iremos inicialmente acessar a página Index.cshtml onde poderemos clicar no link para acessar os usuários.

Como não estamos autenticados seremos direcionados para página de Login (UsuarioLogin.cshtml).

Informando as credenciais corretas estaremos autenticados e poderemos acessar a página para exibir os usuários:

Observe que podemos acessar o cookie armazenado no navegador pressionado F12(No Firefox), e assim, excluindo o cookie, voltamos ao estado inicial de não autenticado.

Pegue o projeto aqui: Aspnet_AuthCookies1.zip (sem as referências)

"Ai dos que ajuntam casa a casa, reúnem campo a campo, até que não haja mais lugar, e fiquem como únicos moradores no meio da terra!"
Isaías 5:8

Referências:


José Carlos Macoratti