> ## Content Index
> Fetch the complete content index at: https://www.azurebrasil.cloud/llms.txt
> Use this file to discover other available public pages before exploring further.

# Gerenciamento de chaves JWT no ASP.NET Core
- URL: https://www.azurebrasil.cloud/blog/gerenciamento-de-chaves-jwt-no-asp-net-core/
- Published: 2026-04-24T12:07:29.000Z
- Updated: 2026-04-24T12:07:29.000Z
- Description: Você configurou JWT Bearer Authentication no ASP.NET Core. Endpoint de login gera o token, `[Authorize]` protege a controller, `AddJwtBearer` valida a assinatura. Funciona. Cola no `Program.cs`, roda, token sai no response. Bonito. Agora me responde: como você gerencia a chave de assinatura?
- Author: Bruno Brito
- Tags: aspnet, segurança

Se a resposta envolve `Encoding.UTF8.GetBytes("minha-chave-secreta")`, isso nem é uma chave, é uma senha disfarçada e é digno de um tapa na cara.

Você tem uma chave simétrica. HMAC-SHA256\. O mesmo segredo precisa existir em todo lugar que valida o token. Se sua API roda em três instâncias atrás de um load balancer, as três precisam da mesma chave. Se você tem uma API de identidade e três APIs consumidoras, todas compartilham o mesmo segredo. Se esse segredo vaza, tudo está comprometido. E rotação? Troca a chave e todo token emitido antes morre na hora. Sem transição, sem graceful rotation.

![](https://storage.ghost.io/c/00/52/0052dced-0017-4d07-b190-1f5c48e0ab59/content/images/2026/04/image-17.png)

Na prática, HMAC funciona pra cenários simples. Uma API, um servidor, um ambiente de dev. Mas quando o sistema cresce — e cresce — você precisa de chaves assimétricas, rotação automática e um jeito de distribuir a chave pública sem expor a privada. É aí que entra o `NetDevPack.Security.Jwt`.

## O que o componente resolve

`NetDevPack.Security.Jwt` é um gerenciador de chaves criptográficas para JWT no .NET. Ele faz o que você teria que implementar na mão:

- Gera chaves RSA ou ECDsa automaticamente — algoritmos assimétricos, conforme recomendação da [RFC 7518](https://datatracker.ietf.org/doc/html/rfc7518?ref=azurebrasil.cloud)
- Rotaciona a chave a cada 90 dias, seguindo as práticas do NIST para rotação de chaves públicas
- Remove chaves privadas antigas após a rotação
- Expõe um endpoint `jwks_uri` com a chave pública no formato JWKS — para que outras APIs validem tokens sem precisar da chave privada
- Persiste as chaves no mesmo local que o ASP.NET Core DataProtection (ou em banco, ou em filesystem)

Você para de gerenciar chaves e passa a só gerar tokens.

Se sua API roda em Kubernetes, Docker Swarm, ou qualquer cenário com múltiplas instâncias, pense nele como o DataProtection Key do ASP.NET Core, só que para JWT. Se você já configurou DataProtection pra funcionar com Redis ou SQL Server em cluster, sabe a dor. Mesma ideia, aplicada à assinatura de tokens.

## Pré-requisitos

- .NET 8+ (o componente suporta a versão mais recente)
- Um projeto ASP.NET Core Web API
- Familiaridade básica com JWT Bearer Authentication (`JwtSecurityTokenHandler`, `SecurityTokenDescriptor`, `AddJwtBearer`)

## Instalação

Dois pacotes. O core e o de AspNetCore:

```bash
dotnet add package NetDevPack.Security.Jwt
dotnet add package NetDevPack.Security.Jwt.AspNetCore

```

O primeiro tem a lógica de gerenciamento de chaves. O segundo adiciona extensions para ASP.NET Core — o endpoint JWKS e a integração com o middleware de validação.

## Configuração básica — uma API que emite e valida

Vamos começar pelo cenário mais comum: uma API que gera tokens JWT e ela mesma valida.

Na maioria dos tutoriais, o `Program.cs` tem uma chave simétrica registrada manualmente, o `AddJwtBearer` com `IssuerSigningKey` explícita, e o endpoint de login montando o token na mão. Com o `NetDevPack.Security.Jwt`, a configuração muda: você não gerencia mais a chave, e a validação usa o JWKS manager.

```csharp
// Program.cs — .NET 8+
var builder = WebApplication.CreateBuilder(args);

// Configura autenticação JWT Bearer
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(options =>
    {
        options.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateIssuer = true,
            ValidateAudience = true,
            ValidateLifetime = true,
            ValidateIssuerSigningKey = true,
            ValidIssuer = builder.Configuration["Jwt:Issuer"],
            ValidAudience = builder.Configuration["Jwt:Audience"]
        };
    });

// Registra o gerenciador de chaves JWT e integra validação
builder.Services.AddJwksManager().UseJwtValidation();

builder.Services.AddAuthorization();

var app = builder.Build();

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

// Expõe o endpoint JWKS com a chave pública
app.UseJwksDiscovery();

```

Perceba o que não existe aqui: não tem `SymmetricSecurityKey`, não tem `RandomNumberGenerator.GetBytes(32)`, não tem `IssuerSigningKey` no `TokenValidationParameters`. O `AddJwksManager()` registra o serviço `IJwtService` no container de DI. O `UseJwtValidation()` conecta a validação de tokens ao gerenciador — ele sabe qual chave está ativa. E o `UseJwksDiscovery()` expõe `https://sua-api/jwks` com a chave pública no formato JWKS.

Por padrão, o componente gera chaves ECDsa com P-256 e SHA-256\. Curva elíptica. Mais seguro que RSA pra tamanhos de chave equivalentes, e mais rápido pra assinar e verificar. Se você precisa de RSA (compatibilidade, por exemplo), muda na configuração — mostro mais pra frente.

## Gerando tokens com IJwtService

Agora o endpoint de login. Em vez de criar uma `SigningCredentials` na mão, você injeta `IJwtService` e pede a credencial atual:

```csharp
app.MapPost("/auth/login", async (
    LoginRequest request,
    IConfiguration config,
    IJwtService jwtService) =>
{
    // Validação real aqui — banco, Identity, o que for
    if (request.Username != "bruno" || request.Password != "senha-segura")
        return Results.Unauthorized();

    var tokenHandler = new JwtSecurityTokenHandler();

    var key = await jwtService.GetCurrentSigningCredentials();

    var token = tokenHandler.CreateToken(new SecurityTokenDescriptor
    {
        Issuer = config["Jwt:Issuer"],
        Audience = config["Jwt:Audience"],
        Subject = new ClaimsIdentity(new[]
        {
            new Claim(ClaimTypes.Name, request.Username),
            new Claim(ClaimTypes.Role, "Admin"),
            new Claim("department", "Engineering")
        }),
        Expires = DateTime.UtcNow.AddHours(1),
        SigningCredentials = key
    });

    return Results.Ok(new
    {
        token = tokenHandler.WriteToken(token)
    });
});

```

No código a diferença parece pouca. Por baixo, muda tudo. `jwtService.GetCurrentSigningCredentials()` retorna a chave ativa no momento — RSA ou ECDsa, gerada automaticamente. Essa chave rotaciona a cada 90 dias. Quando a rotação acontece, tokens antigos continuam válidos porque o endpoint JWKS expõe tanto a chave atual quanto as anteriores (enquanto não expiram). Tokens novos usam a chave nova. Nenhum token válido morre no processo.

Você vai dizer: "mas e o `JwtSecurityTokenHandler`? Não muda nada?" Não. O handler é o mesmo. O que muda é de onde vem a `SigningCredentials`. Em vez de criar uma `SymmetricSecurityKey` na mão e rezar pra ninguém perder a chave, o componente gera, rotaciona e persiste automaticamente — usando algoritmos assimétricos e removendo chaves privadas antigas.

## O endpoint JWKS — distribuindo a chave pública

Quando você chama `app.UseJwksDiscovery()`, a API expõe um endpoint em `/jwks` (configurável). Esse endpoint retorna algo assim:

```json
{
  "keys": [
    {
      "kty": "EC",
      "use": "sig",
      "kid": "abc123...",
      "crv": "P-256",
      "x": "...",
      "y": "...",
      "alg": "ES256"
    }
  ]
}

```

Isso é um JSON Web Key Set — a coleção de chaves públicas que qualquer API pode consumir pra validar tokens. A chave privada nunca sai do servidor que emite o token. Quem valida só precisa da pública.

Na prática, a diferença entre compartilhar uma chave simétrica (`HMAC-SHA256`) e expor um JWKS é a mesma diferença entre dar a chave da sua casa pra todo mundo ou instalar um interfone. Com HMAC, se uma API consumidora é comprometida, a chave de assinatura vaza — e qualquer um pode forjar tokens. Com chaves assimétricas e JWKS, a API consumidora só tem a chave pública. Pode verificar, mas não pode criar. Se for comprometida, o atacante não assina nada.

## Múltiplas APIs — o cenário real

Até aqui, uma API que emite e valida. Mas na prática, o cenário mais comum é: uma API de identidade emitindo tokens e várias APIs consumidoras validando.

### API de identidade (quem emite o token)

Instala `NetDevPack.Security.Jwt.AspNetCore`:

```csharp
// Program.cs da API de Identidade
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddJwksManager();
builder.Services.AddAuthorization();

var app = builder.Build();

app.UseAuthentication();
app.UseAuthorization();
app.UseJwksDiscovery(); // Expõe /jwks

// Endpoint de login gerando token com IJwtService
app.MapPost("/auth/login", async (
    LoginRequest request,
    IJwtService jwtService) =>
{
    // Validação...
    var tokenHandler = new JwtSecurityTokenHandler();
    var currentIssuer = "https://identity.suaempresa.com";

    var key = await jwtService.GetCurrentSigningCredentials();
    var token = tokenHandler.CreateToken(new SecurityTokenDescriptor
    {
        Issuer = currentIssuer,
        Subject = new ClaimsIdentity(new[]
        {
            new Claim(ClaimTypes.Name, request.Username),
            new Claim(ClaimTypes.Role, "Admin")
        }),
        Expires = DateTime.UtcNow.AddHours(1),
        SigningCredentials = key
    });

    return Results.Ok(new { token = tokenHandler.WriteToken(token) });
});

```

### API consumidora (quem valida o token)

Na API que recebe requests autenticados, instala `NetDevPack.Security.JwtExtensions`:

```bash
dotnet add package NetDevPack.Security.JwtExtensions

```

```csharp
// Program.cs da API consumidora
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(options =>
    {
        options.RequireHttpsMetadata = true;
        options.SaveToken = true;
        options.IncludeErrorDetails = true;
        options.SetJwksOptions(
            new JwkOptions("https://identity.suaempresa.com/jwks"));
    });

builder.Services.AddAuthorization();

var app = builder.Build();

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

app.MapGet("/api/dados", () => "dados protegidos")
    .RequireAuthorization();

```

O `SetJwksOptions` faz o trabalho pesado: a API consumidora busca as chaves públicas do endpoint JWKS da API de identidade, cacheia por 10 minutos, e usa pra validar os tokens. Você não copia chave nenhuma entre projetos e não precisa de variável de ambiente com segredo compartilhado.

Quando a API de identidade rotaciona a chave, o JWKS atualiza. Na próxima vez que o cache expira, a API consumidora pega a chave nova. Você nem percebe.

## Persistência — onde as chaves ficam

Por padrão, `NetDevPack.Security.Jwt` guarda as chaves no mesmo lugar que o ASP.NET Core DataProtection — o `IXmlRepository`. Se você já configurou DataProtection pra um cenário distribuído (Redis, Azure Blob Storage, SQL Server), as chaves JWT seguem o mesmo caminho.

Mas se você precisa de controle mais direto, tem duas opções extras:

### Banco de dados com Entity Framework Core

```bash
dotnet add package NetDevPack.Security.Jwt.Store.EntityFrameworkCore

```

```csharp
// Seu DbContext
public class AppDbContext : DbContext, ISecurityKeyContext
{
    public AppDbContext(DbContextOptions<AppDbContext> options) 
        : base(options) { }

    public DbSet<SecurityKeyWithPrivate> SecurityKeys { get; set; }
}

// No Program.cs
builder.Services.AddJwksManager()
    .PersistKeysToDatabaseStore<AppDbContext>();

```

As chaves vão pra uma tabela no banco. Todas as instâncias da API leem a mesma chave. Rode o migration e pronto.

### Filesystem

```bash
dotnet add package NetDevPack.Security.Jwt.Store.FileSystem

```

```csharp
builder.Services.AddJwksManager()
    .PersistKeysToFileSystem(new DirectoryInfo(@"/var/keys/jwt"));

```

Funciona pra cenários onde você tem um volume compartilhado (NFS, Azure Files, EFS). Simples, mas depende do filesystem estar acessível por todas as instâncias.

## Trocando o algoritmo

O padrão é ECDsa com P-256 e SHA-256 (`ES256`). Se precisa de RSA — talvez por compatibilidade com um sistema legado, ou porque seu API gateway só suporta RSA — muda na configuração:

```csharp
builder.Services.AddJwksManager(options =>
{
    options.Jws = Algorithm.Create(DigitalSignaturesAlgorithm.RsaSha256);
});

```

Tem opções pra todos os algoritmos da RFC 7518: RSA (RS256, RS384, RS512), RSA-PSS (PS256, PS384, PS512), ECDsa (ES256, ES384, ES512), e até HMAC (HS256, HS384, HS512) — embora HMAC com esse componente não faça muito sentido, já que o benefício principal é criptografia assimétrica.

Pra JWE (tokens encriptados, não só assinados):

```csharp
builder.Services.AddJwksManager(options =>
{
    options.Jws = Algorithm.Create(DigitalSignaturesAlgorithm.RsaSsaPssSha256);
    options.Jwe = Algorithm.Create(EncryptionAlgorithmKey.RsaOAEP)
        .WithContentEncryption(EncryptionAlgorithmContent.Aes128CbcHmacSha256);
});

```

## O que muda na prática

Comparando com a abordagem manual que todo tutorial ensina:

| Aspecto                   | Chave simétrica manual                       | Com NetDevPack.Security.Jwt                            |
| ------------------------- | -------------------------------------------- | ------------------------------------------------------ |
| Tipo de chave             | Simétrica (HMAC-SHA256)                      | Assimétrica (ECDsa ou RSA)                             |
| Geração                   | RandomNumberGenerator.GetBytes(32) na mão    | Automática via componente                              |
| Rotação                   | Não existe — troca a chave, tokens morrem    | A cada 90 dias, com transição graceful                 |
| Distribuição              | Todas as APIs compartilham o segredo         | Só a chave pública é exposta via JWKS                  |
| Load balancer             | Precisa sincronizar a chave entre instâncias | Centralizado no store (DB, filesystem, DataProtection) |
| Segurança se comprometido | Atacante pode validar E criar tokens         | Atacante só pode validar, não criar                    |
| Configuração              | \~20 linhas de setup manual                  | AddJwksManager() \+ UseJwtValidation()                 |

A abordagem manual funciona. E entender o que acontece por baixo é importante — saber como uma `SigningCredentials` funciona, o que é uma `SecurityKey`, por que a entropia da chave importa. Mas em produção, gerenciamento manual de chaves criptográficas é uma responsabilidade que você não quer carregar.

## Quando não usar

Depende do contexto, como sempre.

Se sua aplicação é um monolito server-rendered com Cookie Authentication, você não precisa disso. Cookies não dependem de chaves JWT — o ASP.NET Core DataProtection já cuida da criptografia dos cookies.

Se você está em um cenário de desenvolvimento local, uma API, sem load balancer, sem múltiplos consumidores — uma chave simétrica no `appsettings.json` resolve. Não over-engineer.

Se você usa um identity provider externo (Keycloak, Auth0, Azure AD), ele já gerencia as chaves e expõe o JWKS dele. Você só consome. O `NetDevPack.Security.Jwt` é pra quando a sua API é o identity provider.

## Conclusão

A maioria dos tutoriais de JWT para em "gera o token, valida, funciona, tchau". O gerenciamento de chaves fica como exercício pro leitor. Só que gerenciamento de chaves não é detalhe de infraestrutura. Chave vazada permite forjar tokens, chave que não rotaciona acumula exposição, e quando o segredo é simétrico e compartilhado entre serviços, qualquer brecha compromete tudo junto.

Se sua API emite tokens JWT, pare de gerenciar chaves na mão. `NetDevPack.Security.Jwt` resolve essa parte. Instala, configura, e segue trabalhando no que importa.

Próximo passo: troque a chave simétrica do seu projeto pelo `IJwtService`, adicione o endpoint JWKS, e acesse `/jwks` no browser.