ASP .NET Core - Usando Cookies


 Hoje vamos recordar o conceito de cookies e seu funcionamento na ASP .NET Core 5.0.

O protocolo HTTP cuida da comunicação entre um servidor e um cliente na web, é conhecido como protocolo sem estado. Em outras palavras, se um usuário solicitar duas páginas em um servidor, nenhuma informação será compartilhada entre essas duas solicitações automaticamente.

Em vez disso, você como desenvolvedor terá que usar um recurso para compartilhar as informações, e, um dos recursos mais básicos usados para este fim são os cookies (temos também as sessions).

O que é um cookie?

Um cookie é basicamente um arquivo físico de texto simples armazenado pelo cliente (geralmente um navegador), vinculado a um site específico. O cliente irá então permitir que este site específico leia as informações armazenadas neste arquivo em requisições subsequentes, permitindo que o servidor (ou mesmo o próprio cliente) armazene informações para uso posterior.

Na ASP .NET Core podemos criar e gerenciar os cookies usando a classe CookieOptions.

No trecho de código a seguir estamos criando um cookie que não pode ser acessado por um script do lado do cliente (HttpOnly=true) e definindo uma data de expiração de 2 horas para o token:

    var cookieOptions = new CookieOptions
    {
          HttpOnly = true,
          Expires = DateTime.UtcNow.AddHours(2),
     };

     Response.Cookies.Append("jwtCookie", 'valor', cookieOptions);

Observe que usamos a propriedade Response na classe HttpContext, onde podemos acessar a propriedade Cookies.

A seguir, usando o método Append(), adicionamos um Cookie à saída, com o nome jwtCookie e atribuindo um valor ao cookie.

Para recuperar o valor deste cookie podemos usar a propriedade Request:

     var jwt = Request.Cookies["jwtCookie"];

Aqui usamos a propriedade Request para ler o valor do cookie - o motivo é que a configuração de um cookie é feita adicionando informações à resposta enviada ao cliente, enquanto a leitura significa que você está obtendo informações do request feito pelo cliente (um navegador normal enviará automaticamente todos os cookies com cada request).

Para mostrar um exemplo básico de uso de cookies vejamos um trecho de código onde verificamos se um usuário esta acessando o um site pela primeira vez ou já visitou o site anteriormente.

public class CookiesController : Controller
{
    public IActionResult Index()
    {
       if(!HttpContext.Request.Cookies.ContainsKey("primeiro_request"))
      {
           HttpContext.Response.Cookies.Append("primeiro_request", DateTime.Now.ToString());
           return Content("Bem-Vindo, novo visitante!");
      }
      else
      {
           DateTime primeiroRequest = DateTime.Parse(HttpContext.Request.Cookies["primeiro_request"]);

           return Content("Bem-VIndo de Volta , sua primeira visita foi em : " + primeiroRequest.ToString());
       }
    }
}

No código da Action Index do controlador CookiesController temos o seguinte código:

1- Verificamos se existe um cookie com o nome 'primeiro_request' na coleção de cookies do Request usando a proprieade ContainsKey :

         if(!HttpContext.Request.Cookies.ContainsKey("primeiro_request"))

2- Se não existir o cookie então criamos o cookie 'primeiro_request' e atribuímos uma data exibindo uma mensagem:

         HttpContext.Response.Cookies.Append("primeiro_request", DateTime.Now.ToString());
         return Content("Bem-Vindo, novo visitante!");


3- Se o cookie já existir então obtemos a data atribuída ao cookie e emitimos uma mensagem de boas vindas indicando a data a primeira visita:

         DateTime primeiroRequest = DateTime.Parse(HttpContext.Request.Cookies["primeiro_request"]);
         return Content("Bem-VIndo de Volta , sua primeira visita foi em : " + primeiroRequest.ToString());

A classe CookieOptions

Quando criamos um cookie usando o método Append podemos passar uma instância da classe CookieOptions conforme mostramos na criação do cookie jwtCookie:  

 var cookieOptions = new CookieOptions
    {
          HttpOnly = true,
          Expires = DateTime.UtcNow.AddHours(2),
     };

     Response.Cookies.Append("jwtCookie", 'valor', cookieOptions);
 

Ela permite que você ajuste vários aspectos importantes do seu cookie, por exemplo, quanto tempo deve permanecer ativo e coisas como domínio (s) e caminho.

Vejamos a seguir as principais propriedades da classe CookieOptions que esta presente no namespace Microsoft.AspNetCore.Http.

Podemos criar uma instância da classe CookieOptions e passar as valores padrão usando o método Append:

CookieOptions cookieOptions = new CookieOptions();            
HttpContext.Response.Cookies.Append("primeiro_request", DateTime.Now.ToString(), cookieOptions);

A seguir podemos definir outras propriedades como:

CookieOptions.Expires

Por padrão, seu cookie será um chamado cookie de sessão, o que significa que só existirá enquanto o navegador permanecer aberto - uma vez que o navegador é fechado, o cookie é excluído pelo navegador.

No entanto, você é livre para alterar esse comportamento usando a propriedade Expires. Esta propriedade é uma instância DateTimeOffset, o que facilita a configuração de um tempo de expiração, como este:

cookieOptions.Expires = new DateTimeOffset(DateTime.Now.AddDays(7));

Isso fará com que o cookie expire em 7 dias. Você pode ajustar isso usando métodos como AddDays(), AddHours(), etc.

CookieOptions.Domain

Por padrão, o cookie será definido para o domínio que fez a solicitação. Portanto, se sua página for acessada pelo domínio meuwebsite.com, o cookie será definido para meuwebsite.com.

Se sua página for acessada usando um subdomínio, este subdomínio será usado, e isso é importante, porque um subdomínio pode ser "www". Portanto, se sua página for acessada por www.meuwebsite.com, o cookie será, por padrão, acessível apenas em www.meuwebsite.com e NÃO em meuwebsite.com. Portanto, pode ser uma boa ideia definir a propriedade Domain para o domínio-base do seu site, prefixado com um ponto da seguinte forma:

cookieOptions.Domain = ".meuwebsite.com";

Agora seu cookie estará acessível em meuwebsite.com, bem como em todos os subdomínios possíveis. Por outro lado, se você não tiver controle total sobre o domínio, pode limitar o domínio do cookie ao (sub) domínio específico que você controla.

CookieOptions.Path

Por padrão, o caminho do cookie será definido como "/", o que basicamente significa que o cookie será válido para todas as páginas do site. No entanto, sob certas condições, você pode precisar de um cookie válido apenas para uma página ou pasta específica. Isso é realizado usando a propriedade Path:

cookieOptions.Path = "/usuarios/";

Com isso definido, o cookie agora só ficará visível e legível para as páginas da pasta "usuarios", bem como para as subpastas dela.

CookieOptions.HttpOnly

Esta propriedade define um valor que indica se o cookie pode ser acessível por script do lado do cliente. Os valores possíveis são true e false.

Ao definir a propriedade HttpOnly com o valor true, ao gerar um cookie,  estamos mitigando o risco de o script do lado do cliente acessar o cookie protegido (se o navegador oferecer suporte).

Como resultado, mesmo que exista uma falha de script entre sites (XSS) e um usuário acesse acidentalmente um link que explora essa falha, o navegador (principalmente o Internet Explorer) não revelará o cookie a terceiros.

Se um navegador não suportar HttpOnly e um site tentar definir um cookie HttpOnly, o sinalizador HttpOnly será ignorado pelo navegador, criando assim um cookie tradicional acessível por script. Como resultado, o cookie (normalmente seu cookie de sessão) torna-se vulnerável a roubo de modificação por script malicioso.

Se um navegador que oferece suporte a HttpOnly detectar um cookie contendo o sinalizador HttpOnly e o código de script do lado do cliente tentar ler o cookie, o navegador retornará uma string vazia como resultado. Isso faz com que o ataque falhe, evitando que o código malicioso (geralmente XSS) envie os dados para o site de um invasor.

CookieOptions.IsEssential

A propriedade IsEssential indica se este cookie é essencial para que o aplicativo funcione corretamente. Se o valor for igual a true, as verificações da política de consentimento podem ser ignoradas. O valor padrão é false.

As verificações da política de consentimento são definidas em CookiePolicyOptions onde se opção CheckConsentNeeded for definida com valor true irá impedir que cookies não essenciais sejam enviados ao navegador (sem cabeçalho Set-Cookie) sem a permissão explícita do usuário.

services.Configure<CookiePolicyOptions>(options =>
{
      // This lambda determines whether user consent
      // for non-essential cookies is needed for a given request.

      options.CheckConsentNeeded = context => true;
      options.MinimumSameSitePolicy = SameSiteMode.None;
 });

Você pode alterar esse comportamento ou marcar seu cookie como essencial, definindo a propriedade IsEssential como true ao criá-lo:

var options = new CookieOptions
{
    Expires = DateTime.Now.AddMinutes(60),
    IsEssential = true
};

Response.Cookies.Append("cookieAgressivo", "Quem manda aqui sou eu...", options);

Veja mais detalhes na documentação em : https://docs.microsoft.com/en-us/aspnet/core/security/gdpr?view=aspnetcore-5.0

Assim graças aos cookies você pode salvar informações sobre o visitante e recuperá-las novamente em solicitações subsequentes. Esta é uma técnica muito importante, utilizada em diversas situações, como manter o usuário logado, rastrear o uso do seu site e muito mais.

E estamos conversados...

"(Disse Jesus) Eu sou a videira verdadeira, e meu Pai é o agricultor. Todo ramo que, estando em mim, não dá fruto, ele corta; e todo que dá fruto ele poda, para que dê mais fruto ainda. "
Joao 15:1

Referências:


José Carlos Macoratti