Guia prático de segurança com ASP.NET Core, autenticação, autorização, validação, rate limiting e proteção contra vulnerabilidades
Em uma arquitetura moderna baseada em APIs REST, implementar endpoints funcionais não é suficiente. Precisamos garantir que cada requisição seja autenticada, autorizada, validada e monitorada, reduzindo os riscos de exposição de dados e exploração de vulnerabilidades.
O OWASP API Security Top 10 reúne dez categorias importantes de riscos específicos de APIs, incluindo falhas de autorização, autenticação inadequada, consumo irrestrito de recursos, configurações inseguras e consumo inseguro de serviços externos.
OWASP API Security Top 10
Neste artigo, vamos demonstrar como aplicar esses princípios em uma API desenvolvida com .NET 10 e ASP.NET Core, utilizando código C#, políticas de segurança e boas práticas que podem ser adaptadas a aplicações corporativas e arquiteturas de microserviços.
O objetivo é construir uma base reutilizável para projetos que precisam evoluir com segurança desde o início.
Autenticação e autorização
Identificar o usuário e controlar o acesso aos recursos e operações.
Proteção contra abuso
Limitar requisições, validar entradas e reduzir o consumo excessivo de recursos.
Monitoramento e auditoria
Registrar eventos relevantes sem expor senhas, tokens ou dados sensíveis.
🧭 1. Entendendo o OWASP API Security Top 10
A edição de referência utilizada neste artigo é a OWASP API Security Top 10 — 2023. Ela organiza os principais riscos específicos de APIs da seguinte maneira.
| Categoria | Risco | Exemplo de proteção |
|---|---|---|
| API1 | Falha de autorização em nível de objeto | Verificar se o usuário pode acessar aquele pedido |
| API2 | Falha de autenticação | Validar tokens e configurar corretamente o provedor de identidade |
| API3 | Falha de autorização em propriedades | Controlar quais campos podem ser lidos ou alterados |
| API4 | Consumo irrestrito de recursos | Rate limiting, paginação e limites de payload |
| API5 | Falha de autorização em nível de função | Restringir operações administrativas |
| API6 | Acesso irrestrito a fluxos de negócio sensíveis | Limitar tentativas e automatização de operações críticas |
| API7 | Server-Side Request Forgery (SSRF) | Restringir destinos de requisições externas |
| API8 | Configuração insegura | Revisar CORS, HTTPS, segredos e ambientes |
| API9 | Gerenciamento inadequado de inventário | Controlar endpoints e versões publicadas |
| API10 | Consumo inseguro de APIs | Validar respostas e tratar falhas de terceiros |
A regra principal é que segurança não deve depender de uma única biblioteca ou middleware. É necessário combinar controles de infraestrutura, aplicação, identidade e regras de negócio.
🏗️ 2. Criando o projeto .NET 10
Vamos desenvolver uma API de pedidos para demonstrar os controles de segurança. O exemplo será baseado em Minimal APIs, permitindo concentrar a implementação em poucos arquivos.
Crie o projeto:
dotnet new webapi -n SecureOrdersApi
cd SecureOrdersApi
Para esse cenário, utilizaremos:
- ASP.NET Core: endpoints e pipeline HTTP.
- JWT Bearer: validação de tokens emitidos por um provedor de identidade.
- Authorization Policies: regras de acesso.
- Data Annotations: validação dos dados recebidos.
- Rate Limiting: controle de volume de requisições.
- Problem Details: respostas de erro padronizadas.
- ILogger: registro de eventos operacionais.
O ASP.NET Core oferece recursos nativos de autenticação, autorização, HTTPS, proteção de dados e outros controles de segurança.
🔐 3. Implementando autenticação com JWT
O primeiro controle é verificar a identidade de quem acessa a API.
Instale o pacote de autenticação:
dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer
Um token JWT deve ser emitido por um provedor de identidade confiável, como um serviço compatível com OAuth 2.0 e OpenID Connect. A API deve validar assinatura, emissor, audiência e expiração do token.
Configure a autenticação no Program.cs:
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization;
var builder = WebApplication.CreateBuilder(args);
var authority = builder.Configuration["Authentication:Authority"]
?? throw new InvalidOperationException(
"Authentication:Authority não configurado.");
var audience = builder.Configuration["Authentication:Audience"]
?? throw new InvalidOperationException(
"Authentication:Audience não configurado.");
builder.Services
.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.Authority = authority;
options.Audience = audience;
});
builder.Services.AddAuthorizationBuilder()
.SetFallbackPolicy(
new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build());
var app = builder.Build();
app.UseHttpsRedirection();
app.UseAuthentication();
app.UseAuthorization();
app.MapGet("/api/private", () =>
Results.Ok(new { message = "Acesso autorizado." }));
app.Run();
Configure Authentication:Authority e Authentication:Audience com os valores reais do seu provedor de identidade, por exemplo por meio de variáveis de ambiente.
A política de fallback exige autenticação para endpoints que não tenham uma política de autorização explícita. Endpoints públicos devem ser marcados conscientemente com .AllowAnonymous().
🧪 Resultado esperado
| Cenário | Resposta |
|---|---|
| Sem token em endpoint protegido | 401 Unauthorized |
| Token inválido ou expirado | 401 Unauthorized |
| Token válido e acesso permitido | 200 OK |
| Usuário autenticado sem permissão | 403 Forbidden |
Autenticação não significa autorização: saber quem é o usuário não garante que ele possa acessar qualquer recurso.
🛡️ 4. API1 e API5 — protegendo recursos e funções
Um dos erros mais comuns em APIs é permitir que um usuário autenticado consulte dados de outra pessoa apenas alterando o identificador enviado na URL.
Imagine a seguinte requisição:
GET /api/orders/200
Authorization: Bearer <access_token>
O fato de o usuário possuir um token válido não significa que ele seja proprietário do pedido 200.
❌ Implementação vulnerável
app.MapGet("/api/orders/{id:int}", (int id) =>
{
// Vulnerável: não verifica a propriedade do pedido.
return Results.Ok(new { Id = id });
});
✅ Implementação com autorização por recurso
Para demonstrar a regra, vamos utilizar uma lista em memória. Em produção, a consulta deve ser feita pelo repositório ou banco de dados.
var orders = new[]
{
new Order(100, "user-123", "Paid"),
new Order(200, "user-456", "Pending")
};
app.MapGet("/api/orders/{id:int}", (
int id,
HttpContext context) =>
{
var subject = context.User.FindFirst("sub")?.Value;
if (string.IsNullOrWhiteSpace(subject))
return Results.Forbid();
var order = orders.FirstOrDefault(x => x.Id == id);
if (order is null || order.OwnerId != subject)
return Results.NotFound();
return Results.Ok(new
{
order.Id,
order.Status
});
}).RequireAuthorization();
record Order(int Id, string OwnerId, string Status);
Nesse exemplo, a API retorna 404 Not Found quando o pedido não existe ou não pertence ao usuário, evitando revelar a existência de recursos alheios.
Importante: o exemplo pressupõe que o provedor de identidade emite uma claim sub estável e confiável. Em sistemas reais, a associação entre identidade e proprietário deve ser feita com base no identificador validado pelo servidor.
A OWASP recomenda verificar a autorização em todas as operações que acessam dados utilizando identificadores fornecidos pelo cliente.
🔑 Protegendo funções administrativas
Para restringir operações administrativas, utilize políticas específicas:
builder.Services.AddAuthorizationBuilder()
.AddPolicy("AdminOnly", policy =>
policy.RequireRole("Admin"));
E aplique a política:
app.MapDelete("/api/orders/{id:int}", (int id) =>
{
return Results.NoContent();
})
.RequireAuthorization("AdminOnly");
Essa regra demonstra o controle de acesso por função. Para autorização em nível de objeto, a verificação de propriedade ou permissão específica continua necessária.
📦 5. API3 — evitando exposição excessiva e Mass Assignment
Outro risco é aceitar diretamente entidades de domínio ou permitir que o cliente altere propriedades que deveriam ser controladas exclusivamente pelo servidor.
Imagine que o cliente envie:
{
"productName": "Notebook",
"quantity": 2,
"isAdmin": true,
"discount": 100
}
Se a API aceitar indiscriminadamente todas as propriedades, o cliente poderá tentar modificar campos que não deveria controlar.
✅ Utilize DTOs específicos
using System.ComponentModel.DataAnnotations;
public sealed record CreateOrderRequest(
[Required, StringLength(100, MinimumLength = 2)]
string ProductName,
[Range(1, 20)]
int Quantity);
No endpoint, aceite somente os campos permitidos:
app.MapPost("/api/orders", (
CreateOrderRequest request) =>
{
var order = new
{
Id = Guid.NewGuid(),
request.ProductName,
request.Quantity,
Status = "Pending"
};
return Results.Created(
$"/api/orders/{order.Id}", order);
})
.RequireAuthorization();
A validação deve estar efetivamente habilitada e aplicada ao modelo recebido. No .NET 10, Minimal APIs oferecem suporte integrado a validação com AddValidation() e atributos como [Required] e [Range].
Para ativá-la no projeto, instale o pacote de validação, caso necessário:
dotnet add package Microsoft.Extensions.Validation
E registre os serviços:
builder.Services.AddValidation();
Além disso, em uma aplicação real, o serviço de criação precisa verificar estoque, preço, permissões e regras de negócio. Os valores financeiros devem ser calculados no servidor, nunca confiados ao cliente.
🚦 6. API4 — implementando Rate Limiting
Uma API sem controle de volume pode sofrer consumo excessivo de recursos, tentativas automatizadas ou aumento inesperado de custos.
O ASP.NET Core disponibiliza middleware de rate limiting com políticas globais e específicas por endpoint.
Adicione os namespaces:
using System.Threading.RateLimiting;
Configure uma política de limitação:
builder.Services.AddRateLimiter(options =>
{
options.RejectionStatusCode = 429;
options.AddFixedWindowLimiter("api", limiter =>
{
limiter.PermitLimit = 60;
limiter.Window = TimeSpan.FromMinutes(1);
limiter.QueueLimit = 0;
limiter.AutoReplenishment = true;
});
});
Ative o middleware:
app.UseRateLimiter();
Aplique a política:
app.MapGet("/api/products", () =>
{
return Results.Ok(new[]
{
"Notebook",
"Mouse",
"Keyboard"
});
})
.RequireAuthorization()
.RequireRateLimiting("api");
O exemplo permite até 60 requisições por janela de um minuto, conforme a política configurada.
Quando o limite for excedido, a resposta será:
HTTP/1.1 429 Too Many Requests
⚠️ Cuidados em produção
- Ajuste os limites conforme o perfil de consumo real.
- Considere políticas por identidade autenticada ou cliente.
- Não confie cegamente em cabeçalhos de IP enviados pelo cliente.
- Em múltiplas instâncias, avalie uma estratégia distribuída ou um gateway com limites compartilhados.
- Utilize proteção de infraestrutura contra DDoS quando necessário.
Rate limiting na aplicação é uma camada de defesa, não um substituto para proteção de rede.
🔍 7. API7 — prevenção contra SSRF
Server-Side Request Forgery ocorre quando a API é induzida a fazer requisições para destinos inesperados, por exemplo serviços internos ou endpoints de metadados da infraestrutura.
❌ Evite aceitar qualquer URL do cliente
// Não implemente dessa forma em produção.
app.MapGet("/api/fetch", async (
string url,
HttpClient client) =>
{
return await client.GetStringAsync(url);
});
Essa abordagem permite que o usuário controle o destino da requisição.
✅ Prefira destinos conhecidos e controlados
builder.Services.AddHttpClient("Catalog", client =>
{
client.BaseAddress =
new Uri("https://catalog.example.com/");
client.Timeout = TimeSpan.FromSeconds(5);
});
Utilize o cliente configurado por meio de IHttpClientFactory e aceite identificadores ou caminhos validados, em vez de URLs arbitrárias.
Para integrações que realmente exigem destinos dinâmicos, implemente uma allowlist de hosts, valide esquema e porta, controle redirecionamentos e bloqueie destinos internos e endereços de metadados. A validação de DNS deve considerar mudanças de resolução e técnicas de DNS rebinding.
A OWASP identifica SSRF como um dos riscos específicos de segurança de APIs.
⚙️ 8. API8 — configuração segura do ASP.NET Core
Uma API pode conter código correto e ainda ser vulnerável por causa de configurações inadequadas.
🔒 HTTPS
Mantenha o redirecionamento para HTTPS:
app.UseHttpsRedirection();
Em produção, configure corretamente certificados TLS e o proxy reverso. A terminação TLS e os cabeçalhos encaminhados devem ser configurados conforme a infraestrutura confiável.
🌐 CORS
Configure explicitamente os origens autorizados quando a API for consumida por aplicações web em outros domínios:
builder.Services.AddCors(options =>
{
options.AddPolicy("Frontend", policy =>
{
policy
.WithOrigins("https://app.example.com")
.WithMethods("GET", "POST", "PUT", "DELETE")
.WithHeaders("Authorization", "Content-Type");
});
});
Ative a política:
app.UseCors("Frontend");
CORS controla quais origens de navegador podem ler respostas; não é autenticação e não bloqueia, por si só, chamadas feitas por clientes fora do navegador.
🔑 Segredos e configurações
- Não armazene senhas, chaves privadas ou tokens no repositório.
- Utilize um gerenciador de segredos em produção.
- Mantenha ambientes de desenvolvimento e produção separados.
- Desative páginas de erro detalhadas e ferramentas de diagnóstico públicas em produção.
- Restrinja as permissões de acesso ao banco de dados e aos serviços externos.
A documentação oficial do ASP.NET Core reúne orientações sobre HTTPS, autenticação, autorização, CORS e gerenciamento de segredos.
🧾 9. API9 — inventário e versionamento de APIs
Em sistemas corporativos, endpoints antigos, rotas administrativas esquecidas e versões ainda publicadas podem aumentar a superfície de ataque.
Algumas medidas importantes:
- Documentar os endpoints públicos e internos.
- Definir um processo de descontinuação de versões.
- Remover rotas de diagnóstico que não são necessárias em produção.
- Controlar a publicação de documentação OpenAPI.
- Revisar quais APIs estão acessíveis externamente.
- Monitorar versões e dependências fora de suporte.
Por exemplo, uma API pode organizar suas rotas assim:
/api/v1/orders
/api/v1/products
/api/v2/orders
A simples presença de /v1 ou /v2 não implementa versionamento completo: é preciso controlar contratos, compatibilidade, autenticação e o ciclo de vida de cada versão.
A OWASP recomenda manter um inventário atualizado de hosts, endpoints e versões das APIs.
🔗 10. API10 — consumo seguro de APIs externas
Uma resposta de terceiro também deve ser considerada entrada não confiável.
Ao integrar APIs externas:
- Valide os campos e os tipos retornados.
- Defina timeouts.
- Limite o tamanho das respostas.
- Trate erros sem expor detalhes internos.
- Configure retries com cuidado para não multiplicar chamadas.
- Evite encaminhar credenciais para destinos não autorizados.
- Verifique a autenticidade e a integridade dos dados conforme o protocolo utilizado.
Por exemplo:
var response = await client.GetAsync(
"products",
cancellationToken);
if (!response.IsSuccessStatusCode)
{
logger.LogWarning(
"Consulta ao catálogo falhou. Status: {StatusCode}",
(int)response.StatusCode);
return Results.Problem(
statusCode: StatusCodes.Status502BadGateway,
title: "Serviço externo indisponível");
}
O client e o logger devem ser injetados pelo sistema de dependências. O exemplo pressupõe que a requisição e o conteúdo já possuem limites adequados; em produção, também valide o JSON recebido antes de utilizar seus dados.
A categoria API10 da OWASP destaca justamente os riscos de confiar excessivamente em serviços e respostas de terceiros.
📝 11. Logs seguros e tratamento de erros
Logs são importantes para identificar ataques, investigar falhas e entender o comportamento da API. Porém, registrar dados sensíveis pode criar uma nova vulnerabilidade.
Prefira:
logger.LogWarning(
"Acesso negado ao pedido {OrderId}",
orderId);
Evite:
// Não registre tokens, senhas ou dados de cartão.
logger.LogInformation(
"Authorization: {Token}",
accessToken);
Padronize os erros com Problem Details:
builder.Services.AddProblemDetails();
E habilite o middleware:
app.UseExceptionHandler();
app.UseStatusCodePages();
Configure também logging estruturado, correlação de requisições e alertas para eventos relevantes. O objetivo é permitir investigação sem revelar stack traces, informações de infraestrutura ou dados pessoais ao consumidor da API.
🧪 12. Testando as regras de segurança
Uma política de segurança só é útil se pudermos verificar seu comportamento.
Monte testes automatizados para cenários como:
| Teste | Resultado esperado |
|---|---|
| Endpoint privado sem token | 401 |
| Token inválido | 401 |
| Usuário tentando acessar pedido alheio |
404 ou 403, conforme a política |
| Usuário comum tentando executar função administrativa | 403 |
| Payload com valores inválidos | 400 |
| Requisições acima do limite | 429 |
| Serviço externo indisponível | Erro controlado, sem detalhes internos |
| Endpoint descontinuado | Não deve permanecer exposto sem necessidade |
Além dos testes de unidade e integração, considere análise estática, verificação de dependências, testes de segurança dinâmicos e revisão de configurações no pipeline CI/CD.
Os testes devem usar identidades e tokens de teste controlados. Nunca execute testes destrutivos contra sistemas sem autorização.
🏛️ 13. Como organizar as regras em uma arquitetura .NET
Para projetos maiores, evite concentrar toda a segurança em um único Program.cs.
Uma estrutura possível:
SecureOrdersApi/
├── src/
│ ├── SecureOrdersApi.Api/
│ │ ├── Endpoints/
│ │ ├── Middleware/
│ │ ├── Security/
│ │ └── Program.cs
│ ├── SecureOrdersApi.Application/
│ │ ├── Orders/
│ │ ├── Validators/
│ │ └── Authorization/
│ ├── SecureOrdersApi.Domain/
│ │ ├── Entities/
│ │ └── Rules/
│ └── SecureOrdersApi.Infrastructure/
│ ├── Persistence/
│ └── Integrations/
└── tests/
├── UnitTests/
├── IntegrationTests/
└── SecurityTests/
A API deve aplicar os controles HTTP, a camada Application deve orquestrar os casos de uso e a camada Domain deve preservar as regras de negócio. A Infrastructure implementa persistência e integrações.
Essa separação facilita testes, revisão de segurança e manutenção sem confundir autenticação, autorização e validação de negócio.
✅ 14. Checklist de segurança para produção
Checklist OWASP + .NET 10
0 de 10
Validar assinatura, emissor, audiência e expiração dos tokens
Exigir autenticação por padrão nos endpoints privados
Verificar autorização por recurso e por função
Utilizar DTOs e validar entradas
Aplicar rate limiting e limites de payload
Configurar HTTPS, CORS e segredos corretamente
Restringir URLs e destinos de integrações externas
Padronizar erros e não expor informações internas
Manter inventário de endpoints e versões
Automatizar testes de segurança no CI/CD
Limpar checklist
Esse checklist é um ponto de partida, não uma certificação de conformidade. Os controles devem ser avaliados conforme a arquitetura, os dados processados, as ameaças e os requisitos regulatórios de cada sistema.
🎯 Conclusão
Implementar as regras da OWASP em uma API com .NET 10 significa incorporar segurança ao ciclo de desenvolvimento, e não apenas adicionar um middleware no final do projeto.
Com autenticação JWT corretamente configurada, autorização por recurso e função, validação de entradas, rate limiting, tratamento seguro de erros e controle das integrações, criamos uma base muito mais resistente a falhas comuns de APIs.
O passo seguinte é automatizar os testes, revisar as configurações de infraestrutura e incluir verificações de segurança no pipeline de entrega.
Uma API moderna não é apenas aquela que responde rápido. É aquela que responde corretamente, respeita as permissões e protege os dados em todas as etapas.
🤝 Conecte-se Comigo
Se você trabalha com .NET moderno e quer evoluir em arquitetura, C#, segurança de APIs, DevOps ou interoperabilidade, vamos conversar:
Top comments (0)