ASP.NET Core - Autenticação JWT (revisitado)


 Neste artigo vamos recordar como implementar a autenticação JWT em uma aplicação ASP .NET Core.

Como eu já escrevi alguns artigos sobre o assunto vou realizar uma abordagem bem objetiva e direta sem entrar em detalhes nos conceitos envolvidos.

Para saber mais sobre os Json Web Tokens - JWT consulte meus artigos:

Neste artigo vamos criar uma API ASP .NET Core, proteger os seus endpoints, gerar o token JWT, registrar usuários e tratar com a autorização baseada em roles usando o Identity.

Recursos usados:

Criando e configurando a API

Para criar um novo projeto de Web API no ambiente .NET Core usando a NET CLI, precisamos:

Vamos abrir um terminal de comandos usando o PowerShell e criar um diretório labs, e entrando dentro desta pasta vamos emitir o comando :  dotnet new webapi -n apiagenda

Este comando vai criar a pasta apiagenda e nesta pasta vai criar o projeto web api.

Temos assim o projeto web api - apiagenda - criado que vai exibir uma lista de agendamentos.

Podemos agora buildar o projeto e a seguir executar a api criada digitando os comandos:

Abrindo um navegador e digitando o comando: https://localhost:5001/swagger/index.html teremos o resultado a seguir:

Temos a nossa apiagenda exibindo um único endpoint usando a interface do Swagger que foi incluída por padrão no template padrão que usamos para criar o projeto. Esse endpoint usa o controlador padrão criado que podemos até remover do projeto pois não vamos usá-lo.

Vamos criar um novo controlador para exibir uma lista de agendamentos.

Vamos abrir o projeto no VS Code digitando o comando : code .

Abaixo vemos o projeto aberto no VS Code:

Agora crie uma pasta Models no projeto e nesta pasta crie a classe Agenda:

    public class Agenda
    {
        public int Id { get; set; }
        public string Evento { get; set; }
        public DateTime Data { get; set; }
    }

A seguir crie um novo controlador na pasta Controllers chamado AgendaController com o seguinte código :

using System.Collections.Generic;
using apiagenda.Models;
using Microsoft.AspNetCore.Mvc;
namespace apiagenda.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class AgendaController : ControllerBase
    {
        [HttpGet]
        public IActionResult GetAgendamentos()
        {
            List<Agenda> agenda = new List<Agenda>();
            agenda.Add(new Agenda()
            {
                Id = 1,
                Evento = "Curso DDD na prática",
                Data = System.DateTime.Now
            });
            agenda.Add(new Agenda()
            {
                 Id = 1,
                Evento = "Evento - Design Patterns",
                Data = System.DateTime.Now.AddDays(15)
            });
            agenda.Add(new Agenda()
            {
                Id = 1,
                Evento = "Palestra - Os recursos do .NET 6.0",
                Data = System.DateTime.Now.AddDays(30)
            });
            return new ObjectResult(agenda);
        }
    }
}

Executando novamente o projeto e acessando o endpoint api/agenda temos o resultado:

Assim vemos que o ao acessar o endpoint api/agenda podemos obter a lista de agendamentos.

Vamos então iniciar a configuração para implementar a autenticação JWT.

Configurando a autenticação JWT

Para configurar a autenticação JWT no .NET Core, precisamos modificar o código do método ConfigureServices da classe Startup do projeto. Esta classe realiza a inicialização da aplicação e assim vamos habilitar a autenticação JWT.

Para isso temos que incluir no projeto o pacote: Microsoft.AspNetCore.Authentication.JwtBearer que contém recursos que habilitam o suporte para a autenticação baseada no JWT.

Abra um terminal de comandos, posicione-se na pasta do projeto e digite:

dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer 

Após isso podemos modificar o método ConfigureServices incluindo o código em destaque para implementar a autenticação JWT:

public void ConfigureServices(IServiceCollection services)
{
      services.AddAuthentication(opt => {
           opt.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
           opt.DefaultChallengeScheme = 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"]))
          };
        });

        services.AddControllers();
        services.AddSwaggerGen(c =>
        {
            c.SwaggerDoc("v1", new OpenApiInfo { Title = "apiagenda", Version = "v1" });
       });
 }

Neste código :

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 serão recuperados a partir do arquivo de configuração appsettings.json que vamos definir a seguir.

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;

Agora temos que habilitar o middleware de autenticação incluindo o código abaixo em azul no método Configure da mesma classe:

       public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
                app.UseSwagger();
                app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "apiagenda v1"));
            }
            app.UseHttpsRedirection();
            app.UseRouting();
            app.UseAuthentication();

            app.UseAuthorization();
            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
            });
        }

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

Definindo a segurança dos endpoints da API

Vamos incluir o atributo [Authorize] no método Action GetAgendamentos da API de forma a restringir o acesso a somente usuários autenticados. Dessa forma somente os usuários que fizerem o Login válido poderão obter uma lista de agendamentos.

using System.Collections.Generic;
using apiagenda.Models;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace apiagenda.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class AgendaController : ControllerBase
    {
        [HttpGet,Authorize]
        public IActionResult GetAgendamentos()
        {
            List<Agenda> agenda = new List<Agenda>();
            agenda.Add(new Agenda()
            {
                Id = 1,
                Evento = "Curso DDD na prática",
                Data = System.DateTime.Now
            });
            agenda.Add(new Agenda()
            {
                 Id = 1,
                Evento = "Evento - Design Patterns",
                Data = System.DateTime.Now.AddDays(15)
            });
            agenda.Add(new Agenda()
            {
                Id = 1,
                Evento = "Palestra - Os recursos do .NET 6.0",
                Data = System.DateTime.Now.AddDays(30)
            });
            return new ObjectResult(agenda);
        }
    }
}

Agora ao tentar acessar o endpoint api/agenda iremos obter o erro : 401 Unauthorized

Precisamos então implementar o login para que o usuário possa se autenticar e acessar o endpoint.

Implementando o login

Para autenticar usuários anônimos, temos que fornecer um endpoint de Login em nossa API para que os usuários possam fazer login e acessar recursos protegidos. Um usuário fornecerá um nome de usuário, uma senha e, se as credenciais forem válidas, emitiremos um token JWT para o cliente solicitante.

Para isso vamos adicionar uma classe LoginViewModel para manter as credenciais do usuário no servidor. A classe LoginViewModel é uma classe simples que contém duas propriedades: UserName e Password. Vamos criar essa classe na pasta Models do projeto:

    public class LoginViewModel
    {
        public string UserName { get; set; }
        public string Password { get; set; }
    }

A seguir vamos criar o controlador AuthenticationController na pasta Controllers onde vamos validar as credenciais do usuário. Se as credenciais forem válidas, emitiremos um token JWT. Para esta demonstração, vamos codificar o nome de usuário como 'macoratti' e a senha como 'Numsey#2021' para implementar um usuário fake e assim simplificar o exemplo. (Aqui poderíamos implementar o Identity e armazenar as credenciais do usuário em um banco de dados.)

Depois de validar as credenciais do usuário, vamos gerar um JWT com uma chave secreta. O JWT usa a chave secreta para gerar a assinatura. E essa chave secreta esta definida no arquivo appsettings.json.

Implementando o controlador AuthenticationController:

using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
using apiagenda.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.IdentityModel.Tokens;
namespace apiagenda.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class AuthenticationController : ControllerBase
    {
        private IConfiguration _config;
        public AuthenticationController(IConfiguration Configuration)
        {
            _config = Configuration;
        }
        [HttpPost, Route("login")]
        public IActionResult Login([FromBody]LoginViewModel user)
        {
            if (user == null)
            {
                return BadRequest("Request do cliente inválido");
            }
            if (user.UserName == "macoratti" && user.Password == "Numsey#2021")
            {
                var _secretKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_config["Jwt:Key"]));
                var _issuer = _config["Jwt:Issuer"];
                var _audience = _config["Jwt:Audience"];
                var signinCredentials = new SigningCredentials(_secretKey, SecurityAlgorithms.HmacSha256);
                var tokeOptions = new JwtSecurityToken(
                    issuer : _issuer,
                    audience: _audience,
                    claims: new List<Claim>(),
                    expires: DateTime.Now.AddMinutes(2),
                    signingCredentials: signinCredentials);
                var tokenString = new JwtSecurityTokenHandler().WriteToken(tokeOptions);
                return Ok(new { Token = tokenString });
            }
            else
            {
                return Unauthorized();
            }
         }
    }
}

Entendendo o código :

Criamos um método Action POST usando o verbo HttPost que vai enviar as credenciais para validação para o servidor.

No método de Login, estamos criando um SymmetricSecretKey com o valor da chave secreta _secretKey definida no arquivo appsettings.json. Em seguida, estamos criando o objeto SigningCredentials e, como argumentos, fornecemos uma chave secreta e o nome do algoritmo que vamos usar para encriptar o token.

As duas primeiras etapas são as etapas padrão com as quais você não precisa se preocupar. A terceira etapa é aquela em que estamos interessados. Na terceira etapa, estamos criando o objeto JwtSecurityToken com alguns parâmetros importantes:

A seguir criamos uma representação string do token JWT chamando o método WriteToken em JwtSecurityTokenHandler. Por fim, estamos retornando o JWT em uma response. Como resposta, criamos um objeto anônimo que contém apenas a propriedade Token.

Agora é só alegria, vamos testar...

Executando o projeto no terminal do VS Code.

A seguir informando o userName - macoratti e password - Numsey#2021 e clicando em Execute teremos o seguinte resultado:

Vemos o token gerado, e, fazendo o download temos o token exibido a seguir:

{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2MjM1NDA3MTIsImlzcyI6Imh0dHBzOi8vb
G9jYWxob3N0OjUwMDEiLCJhdWQiOiJodHRwczovL2xvY2FsaG9zdDo1MDAxIn0.F7WWmzYu8-GU-216
yCS6RFUHW4Fic-rN3HxlnAZeL0U"
}

Assim neste artigo vimos como gerar um token JWT de forma simples em uma aplicação ASP .NET Core usando o VS Code.

Para saber como usar o token com a interface do Swagger veja o artigo :  ASP.NET Core - Usando o token JWT com o Swagger

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

Referências:


José Carlos Macoratti