Implementando Autenticação nas Minimal APIs


 Neste artigo explorar o conceito de AuthGuard , — uma abordagem personalizada para autenticar rotas — e demonstra como proteger um endpoint de API mínima que busca uma lista de produtos de um banco de dados.

Para mostrar isso vamos abordar a criação de uma minimal API, e fazer a adição de um AuthGuard para proteger a rota e a implementação da lógica do guard com trechos de código abrangentes.

  

Pré-requisitos :

- SDK do .NET 6.0 ou posterior
- Um entendimento básico de ASP.NET Core
- EF Core

Configurando o Projeto e o Contexto do Banco de Dados

Criando um novo projeto ASP.NET Core Web API:

dotnet new webapi -n ProductApi
cd ProductApi

Vamos incluir no projeto os pacotes:

dotnet add package Microsoft.EntityFrameworkCore.InMemory --version 9.0.6
Microsoft.AspNetCore.Authentication.JwtBearer

Para fins de demonstração, vamos supor que temos um modelo Product e um DbContext correspondente para interagir com o banco de dados. Aqui está um exemplo simplificado:

public class Product
{
   public int Id { get; set; }
   public string Name { get; set; }
   public decimal Price { get; set; }
}

public class ProductContext : DbContext
{
    public ProductContext(DbContextOptions<ProductContext> options)
     : base(options) { }

    public DbSet<Product> Products { get; set; }
}

Criando uma Minimal API para buscar produtos

Agora vamos criar um endpoint na API para buscar produtos incluindo o código abaixo na classe Program:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddDbContext<ProductContext>(opt =>
   // Para simplificar, usando banco de dados em memória
   opt.UseInMemoryDatabase("ProductsDb"));
   builder.Services.AddAuthorization();
   builder.Services.AddAuthentication();

   var app = builder.Build();

   app.MapGet("/products", async (ProductContext db) =>
        await db.Products.ToListAsync())
        .RequireAuthorization(); // É aqui que adicionaremos nosso AuthGuard

app.UseAuthentication();
app.UseAuthorization();

app.Run();

Estamos usando Entity Framework Core com um banco de dados em memória para demonstração. A parte crucial aqui é o método .RequireAuthorization(), indicando que a rota está protegida e requer autenticação.

Implementando a Lógica do AuthGuard

O recurso AuthGuard no ASP.NET Core essencialmente envolve a configuração de serviços de autenticação e autorização para proteger suas rotas. Precisamos definir esquemas de autenticação e políticas que serão usados por nossa API para autenticar requisições.

Para este exemplo, vamos supor que estamos usando tokens JWT para autenticação. Você normalmente configuraria o serviço de bearer token JWT no Program.cs ou em um método de configuração dedicado:

using Microsoft.EntityFrameworkCore;
using Microsoft.IdentityModel.Tokens;
using System.Text;

...
builder.Services.AddAuthentication("Bearer")
    .AddJwtBearer(options =>
    {
        options.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateIssuer = true,
            ValidateAudience = true,
            ValidateLifetime = true,
            ValidateIssuerSigningKey = true,
            ValidIssuer = builder.Configuration["Jwt:Issuer"],
            ValidAudience = builder.Configuration["Jwt:Audience"],
            IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8
                               .GetBytes(builder.Configuration["Jwt:Key"]))
        };
    });

Para complementar a implementação do AuthGuard no ASP.NET Core com autenticação JWT, você precisará fornecer configurações que incluem o emissor (issuer), o público (audience) e uma chave secreta usada para assinar os tokens.

Essas configurações são tipicamente armazenadas no arquivo appsettings.json do seu projeto ASP.NET Core.

O emissor (Jwt:Issuer) é uma string que identifica a entidade principal que emitiu o JWT. É uma forma de garantir que o token foi emitido por uma autoridade confiável.

Aqui está um exemplo de conteúdo do arquivo appsettings.json com a seção de configurações JWT:

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "AllowedHosts": "*",
  "Jwt": {
    "Key": "minhasupersenhasecretaeocultapraxuxu",
    "Issuer": "https://macoratti.com.br",
    "Audience": "https://macoratti.net"
  }
}

Nesta configuração de exemplo temos:

Jwt:Key é sua chave secreta usada para assinar os tokens JWT. Esta deve ser uma string longa, aleatória e mantida segura.

Jwt:Issuer é o emissor do token JWT. Isso pode ser o domínio da sua aplicação ou qualquer identificador que faça sentido para sua aplicação. Ele é usado para validar o campo iss do token.

Jwt:Audience é o destinatário pretendido do token JWT, tipicamente o domínio ou identificador da sua API. Ele é usado para validar o campo aud do token.

Ao configurar o serviço de bearer token JWT em Program.cs, você usa essas configurações para configurar o TokenValidationParameters. A estrutura do ASP.NET Core lê essas configurações de appsettings.json e as usa para validar os tokens recebidos (conforme mostrado no trecho de código acima).

Autenticando Requisições

Com o AuthGuard (autenticação JWT) configurado, qualquer requisição para /products deve incluir um token JWT válido no cabeçalho Authorization.

Veja como um cliente pode buscar dados da rota protegida:

GET /products HTTP/1.1
Host: localhost:5000
Authorization: Bearer <seu_token_jwt_aqui>

Somente requisições com um token JWT válido poderão acessar os dados. Requisições sem um token ou com um token inválido serão rejeitadas com o código de status 401 Unauthorized.

Ao implementar um AuthGuard usando os mecanismos de autenticação e autorização do ASP.NET Core, você pode proteger seus endpoints de forma eficaz. Este artigo demonstrou uma abordagem prática para adicionar um AuthGuard a uma API mínima para buscar uma lista de produtos de um banco de dados, focando na simplicidade e modularidade dos recursos de segurança do ASP.NET Core.

Observação: a chave para uma segurança eficaz não é apenas adicionar camadas de proteção, mas também entender e configurá-las adequadamente para atender às necessidades da sua aplicação.

Para que um cliente possa acessar nosso endpoint /products protegido, ele primeiro precisa obter um token JWT. Isso é feito geralmente através de um endpoint de login ou autenticação, onde o cliente envia suas credenciais (usuário e senha) e, se válidas, recebe um token de volta.

Acessando a API Protegida (Geração e Uso do Token)

Para que um cliente possa acessar nosso endpoint /products protegido, ele primeiro precisa obter um token JWT. Isso é feito geralmente através de um endpoint de login ou autenticação, onde o cliente envia suas credenciais (usuário e senha) e, se válidas, recebe um token de volta.
1. Criando um Endpoint de Login/Autenticação (para Gerar o Token)

Para fins de demonstração, criaremos um endpoint POST /login muito simples. Em um cenário real, você validaria as credenciais contra um banco de dados de usuários. Aqui, faremos uma validação "mockada".

Passo 1: Adicionar um Modelo para as Credenciais de Login

Crie uma nova classe LoginRequest.cs em seu projeto:

namespace ProductApi;

public class LoginRequest
{
   public string? Username { get; set; }
   public string? Password { get; set; }
}

Passo 2: Configurar a Geração do Token JWT no Program.cs

Precisamos de uma lógica para criar o token. Isso envolve definir as claims (informações sobre o usuário), o tempo de expiração e a assinatura do token usando a chave secreta configurada.

Adicione o seguinte código ao seu Program.cs, antes de var app = builder.Build();:

app.MapPost("/login", (LoginRequest loginRequest) =>
{
    // Em um cenário real, você validaria o username e password
    // contra seu banco de dados ou serviço de identidade.
    // Para esta demonstração, vamos simular um usuário válido.
    if (loginRequest.Username == "macoratti" && loginRequest.Password == "password123")
    {
        var issuer = builder.Configuration["Jwt:Issuer"];
        var audience = builder.Configuration["Jwt:Audience"];
        var key = Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"] 
                ?? throw new InvalidOperationException("JWT Key not configured."));
        var securityKey = new SymmetricSecurityKey(key);
        var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);
        // Adicionar claims (informações sobre o usuário)
        var claims = new[]
        {
            new Claim(JwtRegisteredClaimNames.Sub, loginRequest.Username),
            new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
            new Claim(ClaimTypes.Role, "Admin") // Exemplo de claim de role
        };
        var token = new JwtSecurityToken(
            issuer: issuer,
            audience: audience,
            claims: claims,
            expires: DateTime.Now.AddMinutes(30), // Token expira em 30 minutos
            signingCredentials: credentials);
        var jwtToken = new JwtSecurityTokenHandler().WriteToken(token);
        return Results.Ok(new { Token = jwtToken });
    }
    else
    {
        return Results.Unauthorized(); // 401 Unauthorized
    }
});

Explicação do Endpoint /login:

- Ele recebe um LoginRequest (com Username e Password).
- Faz uma validação "mockada" (testuser, password123).
- Se as credenciais forem válidas, ele constrói um JwtSecurityToken usando as configurações do appsettings.json.
- Adiciona claims (assuntos, IDs, roles) ao token.
- Define um tempo de expiração para o token.
- Assina o token com a chave secreta.
- Retorna o token JWT como uma string na resposta.

2. Acessando a API Protegida Usando o Token

Agora que temos um endpoint para gerar o token, podemos mostrar como um cliente (por exemplo, via Postman, cURL ou código C#) faria as requisições.
Exemplo com cURL (para Linha de Comando)

Passo 1: Obter o Token de Acesso

Primeiro, faça uma requisição POST para o endpoint /login para obter o token:

curl -X POST \
  http://localhost:7098/login \
  -H 'Content-Type: application/json' \
  -d '{
    "username": "testuser",
    "password": "password123"
  }'

A resposta será algo parecido com:

{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ0ZXN0dXNlciIsImp0aSI6Ijkw
YjEwZTc3LTc4ZDUtNGJhMC1hYzQ3LTczZWEwYTdkM2Y5NyIsImh0dHA6Ly9zY2hlbWFzLm
1pY3Jvc29mdC5jb20vd3MvMjAwOC8wNi9pZGVudGl0eS9jbGFpbXMvcm9sZSI6IkFkbWluIi
wiZXhwIjoxNzA3MjYzNDkwLCJpc3MiOiJodHRwczovL3lvdXJkb21haW4uY29tIiwiYXVkIjoiaHR
0cHM6Ly95b3VyYXBpLnlvdXJkb21haW4uY29tIn"
}

Copie o valor da propriedade "token".

Passo 2: Usar o Token para Acessar o Endpoint Protegido (/products)

Agora, use o token obtido no cabeçalho Authorization com o prefixo Bearer:

curl -X GET \ http://localhost:7098/products \ -H 'Authorization: Bearer <seu_token_jwt_copiado_aqui>'

E estamos conversados...  

"Bendito o Deus e Pai de nosso Senhor Jesus Cristo, o qual nos abençoou com todas as bênçãos espirituais nos lugares celestiais em Cristo"
Efésios 1:3

Referências:


José Carlos Macoratti