ASP.NET Core - Implementando Json Web Tokens(JWT) - I


 Neste artigo veremos como implementar a autenticação JWT em aplicações ASP .NET Core.

Embora a autenticação baseada em cookies ainda esteja disponível na ASP .NET Core, a autenticação JWT esta se tornando cada dia mais usada e conhecida.

Então o que é JWT ?

Antes de nos aprofundarmos nos detalhes de implementação a nível de código, vamos entender brevemente alguns conceitos relacionados ao JWT.

JWT significa JSON Web Tokens e, é um padrão aberto (RFC 7519) que define uma forma de passar dados do  cliente para o servidor. O JWT possui muitas vantagens em relação à autenticação tradicional de cookies.

O JWT é mais seguro e também pode ser usado com clientes usam navegadores e também com aplicações mobile. A JWT é uma também opção preferencial para implementar a autenticação em aplicativos de página única (SPA).

Nota: As JWTs podem ser assinadas usando um segredo (com o algoritmo HMAC) ou um par de chaves pública/privada usando RSA ou ECDSA.

Um token JWT consiste em três partes:

  1. Header ou Cabeçalho -  É composto por duas partes:  o tipo do token, que é JWT, e o algoritmo de assinatura que está sendo usado, como HMAC SHA256 ou RSA.
    Exemplo:   {   "alg": "HS256" ,  "typ" : "JWT" }
  1. PayLoad ou Carga útil  - É o corpo do JWT e contém as claims. Claims são declarações sobre uma entidade (normalmente, o usuário) e dados adicionais. Existem três tipos de claims: claims registradas, públicas e privadas.
    Exemplo :   { "sub": "1234567890", "name": "Macoratti", "admin": true }

Obs: O JWT possui palavras reservadas e recomendadas para serem colocadas dentro do payload. São elas:

  1. Signature ou Assinatura - Para criar a parte da assinatura, você precisa pegar o cabeçalho codificado, a carga codificada, um segredo, o algoritmo especificado no cabeçalho e sinalizar isso.
    Exemplo :  HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)

Juntando as 3 partes, a saída gera três strings Base64-URL separadas por pontos que podem ser facilmente transmitidos em ambientes HTML e HTTP, sendo mais compactos quando comparados a padrões baseados em XML, como o SAML.  

Exemplo :  eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibm
                FtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwp
                MeJf36POk6yJV_adQssw5c

Você pode testar o JWT gerado no site : JWT.io

Fonte: https://jwt.io/introduction/

Ao contrário dos cookies, que são passados ​​automaticamente para o servidor, o JWT precisa ser passado de forma explícita para o servidor. Então, um fluxo simplificado de operações seria o seguinte:

1- O cliente envia credenciais de segurança, como nome de usuário e senha, para o servidor para validação;
2- O servidor valida o nome de usuário e senha;
3- Se estiver correto, o servidor gera e emite um
token JWT para o cliente;
4- O cliente recebe o token e o armazena em algum lugar;
5- Ao solicitar qualquer recurso ou ação do servidor, o cliente adiciona o token JWT emitido anteriormente no cabeçalho
Authorization;
6- O servidor lê o cabeçalho de autorização para recuperar o token JWT;
7- Se o token for válido, o servidor executará a ação solicitada pelo cliente;


Então, basta colocar o JWT como um ticket. Se a solicitação recebida tiver um ticket JWT, ela poderá acessar um recurso solicitado.

Etapas necessárias para implementar a autenticação baseada em JWT

Para implementar a autenticação baseada em JWT, precisamos executar as seguintes etapas:

1- Armazenar os detalhes do JWT em um arquivo de configuração;
2- Ativar o esquema de autenticação JWT na inicialização do aplicativo;
3- Criar algum mecanismo que valide o nome de usuário e a senha e emita um JWT;
4- Criar uma API protegida;
5- Invocar a API de um cliente;


Vamos executar estes passos um por um em um projeto ASP .NET Core.

Podemos usar o VS 2017 no Windows ou o VS code no Linux ou Mac (neste caso precisamos ter o .NET Core SDK instalado).

Criando uma aplicação ASP .NET Core - Implementando JWT

Abrindo o VS 2017 (atualizado para a versão 15.9.7) crie um novo projeto ASP .NET Core Web Application do tipo API (usando a versão 2.2 do .NET Core) com o nome AspCore_JWT:

Com o projeto criado vamos agora armazenar os detalhes do JWT em um arquivo de configuração.

Abra o arquivo appsettings.json e inclua uma seção chamada Jwt (o nome pode ser qualquer um) no arquivo conforme mostrado a seguir:

{
  "Jwt": {
    "Key": "aqui voce usa uma chaveSecreta para ser usada para assinar o token",
    "Issuer": "AlgumIssuer",
    "Audience": "AlgumaAudience"
  },
  "Logging": {
    "LogLevel": {
      "Default": "Warning"
    }
  },
  "AllowedHosts": "*"
}

- Key é uma string secreta que é usada para assinatura do token;
- Issuer - Indica a parte que esta emitindo o JWT;
- Audience - Indica os destinatários do JWT;

E como funciona ?

Na autenticação, quando o usuário efetuar o login com êxito usando suas credenciais, um JSON Web Token será retornado caso as credenciais sejam validadas.

Obs: Como os tokens são credenciais, muito cuidado deve ser tomado para evitar problemas de segurança. Em geral, você não deve manter os tokens por mais tempo do que o necessário.

Após isso, sempre que o usuário quiser acessar uma rota ou um recurso protegido, deverá enviar o token JWT no cabeçalho Authorization, usando o esquema do Bearer.

Habilitando o esquema da autenticação JWT no Startup

Vamos abrir a classe Startup e alterar o código do método ConfigureServices() habilitando o JWT:

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
            services.AddAuthentication
                 (JwtBearerDefaults.AuthenticationScheme)
                 .AddJwtBearer(options =>
                  {
                     options.TokenValidationParameters = new TokenValidationParameters
                     {
                       ValidateIssuer = true,
                       ValidateAudience = true,
                       ValidateLifetime = true,
                       ValidateIssuerSigningKey = true,

                       ValidIssuer = Configuration["Jwt:Issuer"],
                       ValidAudience = Configuration["Jwt:Audience"],
                       IssuerSigningKey = new SymmetricSecurityKey
                       (Encoding.UTF8.GetBytes(Configuration["Jwt:Key"]))
                     };
                 });
         }

Invocamos o middleware AddAuthentication() e especificamos o esquema do portador JWT para ser o esquema de autenticação. Também especificamos várias opções para o esquema do portador(bearer) JWT.

Se você observar o objeto TokenValidationParameters, verá que ele indica se o emissor, a audiência, a vida útil e a chave de assinatura devem ser validados ou não. No exemplo usamos definindo como true.

Além disso, também especificamos um emissor válido, um público-alvo válido e uma chave de assinatura válida. Esses valores são recuperados do arquivo de configuração appsettings.json.

A seguir vamos incluir o código abaixo em azul, no método Configure da mesma classe:

        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                   app.UseHsts();
            }

            app.UseHttpsRedirection();

            app.UseAuthentication();

            app.UseMvc();
        }

 

No código chamamos UseAuthentication() para conectar o middleware de autenticação ao pipeline HTTP.

Vamos agora criar uma API que vai validar o usuário e emitir um token JWT.

Criando uma API que valida as credenciais e emite um JWT

Neste artigo vamos validar o nome e a senha de um usuário, e para isso vamos criar uma pasta Models no projeto e nesta pasta criar a classe Usuario que representa o nosso modelo:

    public class Usuario
    {
        public string NomeUsuario { get; set; }
        public string Senha { get; set; }
    }

Agora precisamos de algum mecanismo para validar o nome e a senha do usuário que neste exemplo será uma Web API.

Vamos criar um novo controlador usando o template API Controller - Empty,  chamado SegurancaController na pasta Controllers. Este controlador terá dois métodos privados e uma Action pública.

O primeiro método privado é chamado GerarTokenJWT() e gera um token JWT após validar as credenciais do usuário usando o método ValidarUsuario(), e, isso é feito na Action Login().

Como vamos precisar acessar informações que foram definidas no arquivo de configuração appsettings.json vamos injetar uma instância de Configuration no construtor do controlador e para isso precisamos incluir no método ConfigureServices() da classe Startup para usar o suporte nativa da injeção de dependência:

 public void ConfigureServices(IServiceCollection services)
 {
            services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
            services.AddSingleton<IConfiguration>(Configuration);
            .......
}

Após isso podemos definir o código do controlador SegurancaController conforme a seguir:

using AspCore_JWT.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.IdentityModel.Tokens;
using System;
using System.IdentityModel.Tokens.Jwt;
using System.Text;
namespace AspCore_JWT.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class SegurancaController : ControllerBase
    {
        private IConfiguration _config;
        public SegurancaController(IConfiguration Configuration)
        {
            _config = Configuration;
        }
        [HttpPost]
        public IActionResult Login([FromBody]Usuario loginDetalhes)
        {
            bool resultado = ValidarUsuario(loginDetalhes);
            if (resultado)
            {
                var tokenString = GerarTokenJWT();
                return Ok(new { token = tokenString });
            }
            else
            {
                return Unauthorized();
            }
        }
        private string GerarTokenJWT()
        {
            var issuer = _config["Jwt:Issuer"];
            var audience = _config["Jwt:Audience"];
            var expiry = DateTime.Now.AddMinutes(120);
            var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_config["Jwt:Key"]));
            var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);
            var token = new JwtSecurityToken(issuer: issuer,audience: audience,
expires: DateTime.Now.AddMinutes(120),signingCredentials: credentials);
            var tokenHandler = new JwtSecurityTokenHandler();
            var stringToken = tokenHandler.WriteToken(token);
            return stringToken;
        }
        private bool ValidarUsuario(Usuario loginDetalhes)
        {
            if (loginDetalhes.NomeUsuario == "Macoratti" && loginDetalhes.Senha == "Numsey$19")
            {
                return true;
            }
            else
            {
                return false;
            }
        }
    }
}

Vamos entender o código :

1- No método GerarTokenJWT() recupera o emissor, o público-alvo e a chave do arquivo de configuração.

A seguir cria uma nova SymmetricSecurityKey com base na chave. O objeto SigningCredentials é gerado com base no SymmetricSecurityKey. Observe que usamos o algoritmo HS256 para gerar a assinatura digital.

Agora podemos seguir em frente e criar um token JWT. Isso é feito usando a classe JwtSecurityToken. Passamos o emissor, o público-alvo, um DateTime de expiração para o token e as credenciais de assinatura no construtor.

Queremos o JWT em um formato de string para que possa ser facilmente enviado ao cliente. Isso é feito usando a classe JwtSecurityTokenHandler. O método WriteToken() aceita um JwtSecurityToken criado anteriormente e o retorna como uma string de formato JSON compactada.

2- No método ValidarUsuario() valida se o nome e senha do usuário são válidos. Aqui estamos usando valores fixos no código para facilitar mas você pode usar o Identity ou outra técnica para validar o usuário.

Se as credenciais do usuário forem válidas, retornamos true, caso contrário, retornaremos false. Em vez de retornar true, você também pode retornar detalhes do usuário, como nome de usuário e outras informações. Aqui, por uma questão de simplicidade, não retornamos nenhum desses detalhes.

3 - Na Action Login usamos o atributo [HttpPost], ela será invocada pela aplicação cliente passando os detalhes do usuário : nome e senha.

Para fazer a validação chamamos o método ValidarUsuario(), se as credenciais do usuário forem válidas, chamamos o método GerarTokenJWT() para gerar um token JWT. O token no formato string é retornado ao cliente com o status HTTP de Ok (código de status - 200).

Vamos testar a Action Login e nossa implementação usando o Postman antes de continuar.

Testando o Login com o Postman

Para testar nossa implementação vamos executar o projeto e usar o Postman.

Para instalar o Postman acesse esse link : https://www.getpostman.com/apps ou abra o Google Chrome e digite postam e a seguir clique no link:  Postman - Chrome Web Store

Na janela que será aberta clique no botão - Usar no Chrome:

A seguir registre uma conta e faça o login.

Pronto ! Já podemos usar o Postman.

Para poder fazer o teste, como não temos uma interface com o usuário, vamos simular o envio dos dados via POST passando o nome do usuário(Macoratti) e a senha(Numsey$19) esperados.

Antes de executar você pode desabilitar o https na janela de propriedades da aplicação.

Execute o projeto no VS 2017, a seguir inicie o Postman e informe os seguintes dados:

Ao clicar no botão Send você terá como resultado o token JWT gerado e exibido no formato : header.payload.signature

Vamos conferir qual o conteúdo do nosso token gerado copiando o valor e navegando ate o o site jwt.io

Localize a seção Encoded em Debugger e copie e cole apenas o valor do token na caixa de texto Encoded.(1)

A seguir localize a seção Verify Signature e copie e cole sua Key do arquivo appsettings em 2. Você deve ver a forma decodificada do token como mostrado abaixo:

Observe a mensagem:  Signature Verified indicando que o token gerado foi validado. Verifique o valor do Header e do Payload.

Muito bem, nossa implementação esta funcionando, e agora vamos continuar criando um API que necessita de segurança e vamos definir um acesso seguro a este recurso usando o token JWT gerado.

Faremos isso na próxima parte do artigo.

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

Ó Senhor, Senhor nosso, quão admirável é o teu nome em toda a terra, pois puseste a tua glória sobre os céus!
Salmos 8:1

Referências:


José Carlos Macoratti