Secrets no .NET | CSharp
- Secrets no .NET | CSharp
Secrets são valores sensíveis usados pela aplicação, por exemplo:
- connection string com usuário/senha
- chave JWT
- API key de serviço externo
- senha de RabbitMQ, Redis, SMTP, banco etc.
- client secret de OAuth/OIDC
- chave de criptografia
A regra principal é: não coloque segredo em código-fonte nem em appsettings.json versionado. A documentação da Microsoft reforça que senhas e dados sensíveis não devem ficar em source code ou arquivos de configuração, e que segredos de produção devem ser acessados por um meio controlado, como Azure Key Vault. (Microsoft Learn)
Modelo mental do configuration no .NET
No .NET, a aplicação lê configuração por meio de IConfiguration.
O IConfiguration unifica várias fontes de configuração, como:
appsettings.jsonappsettings.Development.json- variáveis de ambiente
- command-line arguments
- user secrets
- Azure Key Vault
- Azure App Configuration
- arquivos por chave, muito usado com Docker/Kubernetes secrets
- provider customizado
A própria documentação do .NET descreve configuração como um conjunto de providers que leem pares chave/valor de fontes diferentes, como arquivos, variáveis de ambiente e Azure Key Vault. (Microsoft Learn)
No ASP.NET Core, isso também é tratado como parte do modelo padrão de configuração da aplicação. O livro ASP.NET Core 9.0 Essentials destaca que o IConfiguration permite acessar várias fontes, como JSON, variáveis de ambiente, argumentos e user secrets de maneira unificada.
Ordem de precedência
Quando duas fontes possuem a mesma chave, vence a última fonte adicionada. A documentação oficial afirma que a ordem dos configuration providers importa e que, se mais de um provider define a mesma chave, o último provider adicionado é usado. (Microsoft Learn)
Na prática, em uma aplicação ASP.NET Core criada com WebApplication.CreateBuilder(args), você normalmente tem algo nessa linha:
| Ordem aproximada | Fonte | Uso comum |
|---|---|---|
| 1 | appsettings.json | Configuração base |
| 2 | appsettings.{Environment}.json | Configuração por ambiente |
| 3 | User Secrets | Segredos locais em desenvolvimento |
| 4 | Variáveis de ambiente | Configuração em servidor/container |
| 5 | Command-line arguments | Override final na execução |
O User Secrets é registrado depois dos arquivos JSON, então ele sobrescreve valores de appsettings.json e appsettings.{Environment}.json. (Microsoft Learn)
Exemplo:
// appsettings.json
{
"Jwt": {
"Secret": "valor-fraco-local"
}
}
dotnet user-secrets set "Jwt:Secret" "valor-real-local"
Resultado:
builder.Configuration["Jwt:Secret"]
retorna:
valor-real-local
O que colocar no appsettings
O appsettings.json deve guardar estrutura e valores não sensíveis.
Exemplo recomendado:
{
"ConnectionStrings": {
"DefaultConnection": ""
},
"Jwt": {
"Issuer": "comandos-api",
"Audience": "comandos-clientes",
"ExpirationMinutes": 60,
"Secret": ""
},
"RabbitMq": {
"Host": "localhost",
"Port": 5672,
"Username": "",
"Password": "",
"VirtualHost": "/"
}
}
O arquivo mostra quais chaves existem, mas os valores sensíveis reais vêm de outra fonte.
Formato de chave hierárquica
No JSON você escreve assim:
{
"RabbitMq": {
"Host": "localhost",
"Password": "123"
}
}
No IConfiguration, a chave fica assim:
builder.Configuration["RabbitMq:Host"];
builder.Configuration["RabbitMq:Password"];
A documentação do .NET mostra que configurações hierárquicas usam : como delimitador, por exemplo Parent:Child:Name. (Microsoft Learn)
Em variável de ambiente, o recomendado é usar __ no lugar de ::
RabbitMq__Password=senha-real
Jwt__Secret=chave-real
ConnectionStrings__DefaultConnection="Server=..."
O __ é convertido para : pelo provider de variáveis de ambiente.
Possibilidade 1: User Secrets para desenvolvimento local
User Secrets é para desenvolvimento local. Ele não é para produção.
Inicializar no projeto:
dotnet user-secrets init
Isso adiciona no .csproj:
<PropertyGroup>
<UserSecretsId>algum-id-unico</UserSecretsId>
</PropertyGroup>
Adicionar secrets:
dotnet user-secrets set "Jwt:Secret" "minha-chave-super-secreta"
dotnet user-secrets set "RabbitMq:Password" "senha-do-rabbit"
dotnet user-secrets set "ConnectionStrings:DefaultConnection" "Server=localhost;Database=app;User Id=sa;Password=Senha@123;TrustServerCertificate=True"
Listar:
dotnet user-secrets list
Remover um secret:
dotnet user-secrets remove "Jwt:Secret"
Limpar todos:
dotnet user-secrets clear
Ler na aplicação:
var jwtSecret = builder.Configuration["Jwt:Secret"];
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
O Secret Manager faz parte do SDK do .NET e é usado para armazenar segredos no ambiente local do desenvolvedor; ele evita que API keys e dados sensíveis fiquem hard-coded no código.
Possibilidade 2: Variáveis de ambiente
Essa é uma das formas mais comuns em produção, principalmente com Docker, Swarm, Linux service, CI/CD e cloud.
Linux
export Jwt__Secret="chave-real"
export RabbitMq__Host="rabbitmq"
export RabbitMq__Username="app"
export RabbitMq__Password="senha-real"
export ConnectionStrings__DefaultConnection="Server=db;Database=app;User Id=app;Password=senha-real;"
Executar:
dotnet MinhaApi.dll
Windows PowerShell
$env:Jwt__Secret="chave-real"
$env:RabbitMq__Password="senha-real"
$env:ConnectionStrings__DefaultConnection="Server=db;Database=app;User Id=app;Password=senha-real;"
dotnet MinhaApi.dll
Docker Compose
services:
api:
image: minha-api:latest
environment:
ASPNETCORE_ENVIRONMENT: Production
Jwt__Secret: ${JWT_SECRET}
RabbitMq__Host: rabbitmq
RabbitMq__Username: ${RABBITMQ_USERNAME}
RabbitMq__Password: ${RABBITMQ_PASSWORD}
ConnectionStrings__DefaultConnection: ${DEFAULT_CONNECTION}
.env:
JWT_SECRET=chave-real
RABBITMQ_USERNAME=app
RABBITMQ_PASSWORD=senha-real
DEFAULT_CONNECTION=Server=db;Database=app;User Id=app;Password=senha-real;
Atenção: .env com secrets também deve ficar fora do Git.
Possibilidade 3: Docker Secrets / Swarm Secrets
Em Docker Swarm, secrets normalmente são montados como arquivos dentro de:
/run/secrets/<nome-do-secret>
Criar secrets:
printf "senha-rabbit" | docker secret create rabbitmq_password -
printf "chave-jwt-real" | docker secret create jwt_secret -
Usar no stack:
services:
api:
image: minha-api:latest
secrets:
- rabbitmq_password
- jwt_secret
environment:
RabbitMq__Host: rabbitmq
RabbitMq__Username: app
RabbitMq__PasswordFile: /run/secrets/rabbitmq_password
Jwt__SecretFile: /run/secrets/jwt_secret
secrets:
rabbitmq_password:
external: true
jwt_secret:
external: true
No .NET, você pode ler manualmente:
static string? ReadSecretFile(IConfiguration configuration, string key)
{
string? filePath = configuration[$"{key}File"];
if (string.IsNullOrWhiteSpace(filePath))
return configuration[key];
return File.Exists(filePath)
? File.ReadAllText(filePath).Trim()
: null;
}
string? jwtSecret = ReadSecretFile(builder.Configuration, "Jwt:Secret");
string? rabbitPassword = ReadSecretFile(builder.Configuration, "RabbitMq:Password");
Ou pode usar o provider KeyPerFile.
KeyPerFile
Instalar pacote se necessário:
dotnet add package Microsoft.Extensions.Configuration.KeyPerFile
Configurar:
var builder = WebApplication.CreateBuilder(args);
builder.Configuration.AddKeyPerFile(
directoryPath: "/run/secrets",
optional: true,
reloadOnChange: false);
Se existir um arquivo:
/run/secrets/Jwt__Secret
com conteúdo:
chave-real
você pode ler:
var jwtSecret = builder.Configuration["Jwt:Secret"];
O provider Key-per-file aparece na lista oficial de providers de configuração do .NET e lê configuração a partir de arquivos em diretório. (Microsoft Learn)
Possibilidade 4: Azure Key Vault
Para produção em Azure, o mais adequado é Key Vault, especialmente quando você quer:
- rotação de secrets
- controle de acesso por identidade
- auditoria
- evitar secrets dentro da aplicação
- evitar secrets no pipeline
- usar Managed Identity
A documentação oficial descreve o Azure Key Vault configuration provider como o provider usado para carregar valores de configuração a partir de secrets do Key Vault. (Microsoft Learn)
Pacotes:
dotnet add package Azure.Extensions.AspNetCore.Configuration.Secrets
dotnet add package Azure.Identity
Exemplo com DefaultAzureCredential:
using Azure.Identity;
var builder = WebApplication.CreateBuilder(args);
var keyVaultUrl = builder.Configuration["KeyVault:Url"];
if (!string.IsNullOrWhiteSpace(keyVaultUrl))
{
builder.Configuration.AddAzureKeyVault(
new Uri(keyVaultUrl),
new DefaultAzureCredential());
}
appsettings.Production.json:
{
"KeyVault": {
"Url": "https://meu-keyvault.vault.azure.net/"
}
}
Secret no Azure Key Vault:
Jwt--Secret
RabbitMq--Password
ConnectionStrings--DefaultConnection
Por padrão, o provider costuma mapear -- para :.
Leitura:
string? jwtSecret = builder.Configuration["Jwt:Secret"];
string? rabbitPassword = builder.Configuration["RabbitMq:Password"];
string? connection = builder.Configuration.GetConnectionString("DefaultConnection");
Em produção, prefira Managed Identity em vez de ClientId + ClientSecret. A própria documentação do Key Vault mostra o modo com Managed Identity para autenticar a aplicação no Key Vault sem credenciais armazenadas no código ou configuração. (Microsoft Learn)
Possibilidade 5: Azure App Configuration
Azure App Configuration é mais voltado para configuração centralizada, feature flags e configurações dinâmicas.
Diferença prática:
| Recurso | Melhor uso |
|---|---|
| Azure Key Vault | Secrets |
| Azure App Configuration | Configurações não sensíveis, feature flags, parâmetros dinâmicos |
| App Configuration + Key Vault | Config centralizada referenciando secrets do Key Vault |
Exemplo conceitual:
builder.Configuration.AddAzureAppConfiguration(options =>
{
options.Connect(builder.Configuration["AppConfig:ConnectionString"]);
});
Para secrets, prefira que o App Configuration referencie Key Vault, e não que o secret fique diretamente nele.
Possibilidade 6: Command-line arguments
Pode sobrescrever configuração na execução:
dotnet MinhaApi.dll --Jwt:ExpirationMinutes=30 --RabbitMq:Host=rabbitmq-prod
Também funciona para secrets, mas não é a melhor opção para segredos reais, porque argumentos podem aparecer em histórico de shell, logs de processo ou ferramentas de inspeção.
Possibilidade 7: arquivo externo não versionado
Você pode carregar um JSON externo fora do repositório.
var builder = WebApplication.CreateBuilder(args);
builder.Configuration.AddJsonFile(
path: "/etc/minha-api/secrets.json",
optional: true,
reloadOnChange: false);
Arquivo:
{
"Jwt": {
"Secret": "chave-real"
},
"RabbitMq": {
"Password": "senha-real"
}
}
Funciona, mas é inferior a Key Vault, Vault, Docker Secrets ou variáveis de ambiente bem gerenciadas. Use quando sua infraestrutura ainda não tem secret manager.
Possibilidade 8: provider customizado
Você pode criar um provider próprio para buscar configuração de:
- banco de dados
- Redis
- API interna
- HashiCorp Vault
- arquivo criptografado
- serviço interno de configuração
O livro ASP.NET Core 9.0 Essentials mostra que é possível criar um ConfigurationProvider customizado preenchendo um Dictionary<string, string?> e adicionando no pipeline com builder.Configuration.Add(...).
Exemplo simples:
using Microsoft.Extensions.Configuration;
public sealed class MeuConfigurationProvider : ConfigurationProvider
{
public override void Load()
{
Data = new Dictionary<string, string?>
{
["MinhaConfig:Valor"] = "123",
["Jwt:Secret"] = "secret-vindo-de-outra-fonte"
};
}
}
public sealed class MeuConfigurationSource : IConfigurationSource
{
public IConfigurationProvider Build(IConfigurationBuilder builder)
{
return new MeuConfigurationProvider();
}
}
Registrar:
builder.Configuration.Add(new MeuConfigurationSource());
Ler:
var valor = builder.Configuration["MinhaConfig:Valor"];
var secret = builder.Configuration["Jwt:Secret"];
Leitura direta com IConfiguration
Exemplo simples:
var builder = WebApplication.CreateBuilder(args);
string? jwtSecret = builder.Configuration["Jwt:Secret"];
string? rabbitPassword = builder.Configuration["RabbitMq:Password"];
string? connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
Exemplo em endpoint:
app.MapGet("/config-check", (IConfiguration configuration) =>
{
var issuer = configuration["Jwt:Issuer"];
return Results.Ok(new
{
Issuer = issuer,
HasJwtSecret = !string.IsNullOrWhiteSpace(configuration["Jwt:Secret"])
});
});
Evite retornar o valor real do secret em endpoint, log ou exception.
Leitura com Options Pattern
Para projeto sério, prefira Options Pattern.
A documentação oficial recomenda Options Pattern para acesso fortemente tipado a grupos de configuração relacionados e também cita separação de responsabilidades e validação de configuração. (Microsoft Learn)
Classe de configuração
public sealed class JwtOptions
{
public const string SectionName = "Jwt";
public required string Issuer { get; init; }
public required string Audience { get; init; }
public required string Secret { get; init; }
public int ExpirationMinutes { get; init; }
}
appsettings.json
{
"Jwt": {
"Issuer": "comandos-api",
"Audience": "comandos-clientes",
"Secret": "",
"ExpirationMinutes": 60
}
}
Registro
var builder = WebApplication.CreateBuilder(args);
builder.Services
.AddOptions<JwtOptions>()
.Bind(builder.Configuration.GetSection(JwtOptions.SectionName))
.Validate(options => !string.IsNullOrWhiteSpace(options.Secret),
"Jwt:Secret é obrigatório.")
.Validate(options => options.ExpirationMinutes > 0,
"Jwt:ExpirationMinutes deve ser maior que zero.")
.ValidateOnStart();
Uso em service
using Microsoft.Extensions.Options;
public sealed class TokenService
{
private readonly JwtOptions _options;
public TokenService(IOptions<JwtOptions> options)
{
_options = options.Value;
}
public string ObterIssuer()
{
return _options.Issuer;
}
}
IOptions, IOptionsSnapshot e IOptionsMonitor
| Tipo | Quando usar | Observação |
|---|---|---|
IOptions<T> | Configuração estável | Singleton, lê uma vez |
IOptionsSnapshot<T> | Configuração por request | Scoped, útil em ASP.NET Core |
IOptionsMonitor<T> | Configuração que pode mudar em runtime | Singleton, suporta callback |
Exemplo com IOptionsMonitor<T>:
public sealed class JwtWatcher
{
private readonly IDisposable? _listener;
public JwtWatcher(IOptionsMonitor<JwtOptions> monitor)
{
_listener = monitor.OnChange(options =>
{
Console.WriteLine($"Jwt config alterada. Issuer: {options.Issuer}");
});
}
}
Atenção: nem toda fonte suporta reload. Variável de ambiente, por exemplo, normalmente não é dinâmica durante o processo.
Validação forte com DataAnnotations
Pacote:
dotnet add package Microsoft.Extensions.Options.DataAnnotations
Classe:
using System.ComponentModel.DataAnnotations;
public sealed class RabbitMqOptions
{
public const string SectionName = "RabbitMq";
[Required]
public required string Host { get; init; }
[Range(1, 65535)]
public int Port { get; init; } = 5672;
[Required]
public required string Username { get; init; }
[Required]
public required string Password { get; init; }
public string VirtualHost { get; init; } = "/";
}
Registro:
builder.Services
.AddOptions<RabbitMqOptions>()
.Bind(builder.Configuration.GetSection(RabbitMqOptions.SectionName))
.ValidateDataAnnotations()
.ValidateOnStart();
Uso:
public sealed class RabbitMqConnectionFactory
{
private readonly RabbitMqOptions _options;
public RabbitMqConnectionFactory(IOptions<RabbitMqOptions> options)
{
_options = options.Value;
}
}
Usando secrets em autenticação JWT
appsettings.json:
{
"Jwt": {
"Issuer": "comandos-api",
"Audience": "comandos-clientes",
"Secret": "",
"ExpirationMinutes": 60
}
}
User Secrets local:
dotnet user-secrets set "Jwt:Secret" "minha-chave-com-pelo-menos-32-caracteres"
Variável de ambiente em produção:
export Jwt__Secret="chave-real-de-producao-com-tamanho-adequado"
Registro:
using System.Text;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
var jwtSection = builder.Configuration.GetSection(JwtOptions.SectionName);
var jwtOptions = jwtSection.Get<JwtOptions>()
?? throw new InvalidOperationException("Configuração JWT ausente.");
if (string.IsNullOrWhiteSpace(jwtOptions.Secret))
throw new InvalidOperationException("Jwt:Secret não configurado.");
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
byte[] key = Encoding.UTF8.GetBytes(jwtOptions.Secret);
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidIssuer = jwtOptions.Issuer,
ValidateAudience = true,
ValidAudience = jwtOptions.Audience,
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(key),
ValidateLifetime = true,
ClockSkew = TimeSpan.FromSeconds(30)
};
});
Nunca coloque senha dentro do JWT. O livro Advanced ASP.NET Core 8 Security alerta que informações dentro de JWT podem ser lidas por quem tem acesso ao token, porque Base64 não é criptografia.
Usando secrets com EF Core
appsettings.json:
{
"ConnectionStrings": {
"DefaultConnection": ""
}
}
User Secret:
dotnet user-secrets set "ConnectionStrings:DefaultConnection" "Server=localhost;Database=app;User Id=sa;Password=Senha@123;TrustServerCertificate=True"
Program.cs:
using Microsoft.EntityFrameworkCore;
builder.Services.AddDbContext<AppDbContext>(options =>
{
string connectionString = builder.Configuration
.GetConnectionString("DefaultConnection")
?? throw new InvalidOperationException("ConnectionStrings:DefaultConnection não configurada.");
options.UseSqlServer(connectionString);
});
A documentação do EF Core também reforça que connection strings podem vir de appsettings, variável de ambiente, Azure Key Vault ou Secret Manager, mas secrets não devem ser adicionados em arquivos de configuração. (Microsoft Learn)
Usando secrets com RabbitMQ
appsettings.json:
{
"RabbitMq": {
"Host": "localhost",
"Port": 5672,
"Username": "",
"Password": "",
"VirtualHost": "/"
}
}
User Secrets:
dotnet user-secrets set "RabbitMq:Username" "app"
dotnet user-secrets set "RabbitMq:Password" "senha-real"
Options:
public sealed class RabbitMqOptions
{
public const string SectionName = "RabbitMq";
public required string Host { get; init; }
public int Port { get; init; } = 5672;
public required string Username { get; init; }
public required string Password { get; init; }
public string VirtualHost { get; init; } = "/";
}
Registro:
builder.Services
.AddOptions<RabbitMqOptions>()
.Bind(builder.Configuration.GetSection(RabbitMqOptions.SectionName))
.Validate(options => !string.IsNullOrWhiteSpace(options.Host), "RabbitMq:Host obrigatório.")
.Validate(options => !string.IsNullOrWhiteSpace(options.Username), "RabbitMq:Username obrigatório.")
.Validate(options => !string.IsNullOrWhiteSpace(options.Password), "RabbitMq:Password obrigatório.")
.ValidateOnStart();
Uso:
using Microsoft.Extensions.Options;
using RabbitMQ.Client;
public sealed class RabbitMqFactory
{
private readonly RabbitMqOptions _options;
public RabbitMqFactory(IOptions<RabbitMqOptions> options)
{
_options = options.Value;
}
public ConnectionFactory Criar()
{
return new ConnectionFactory
{
HostName = _options.Host,
Port = _options.Port,
UserName = _options.Username,
Password = _options.Password,
VirtualHost = _options.VirtualHost
};
}
}
Estratégia por ambiente
| Ambiente | Forma recomendada | Observação |
|---|---|---|
| Desenvolvimento local | User Secrets | Simples e integrado ao .NET SDK |
| Testes locais com Docker | .env fora do Git ou Docker secrets | Evite commitar .env |
| Homologação | Variáveis de ambiente, Vault ou Key Vault | Depende da infra |
| Produção simples | Variáveis de ambiente protegidas | Aceitável, mas exige controle de acesso |
| Produção mais robusta | Key Vault, HashiCorp Vault, AWS Secrets Manager, GCP Secret Manager | Melhor para auditoria e rotação |
| Docker Swarm | Docker secrets + KeyPerFile | Melhor que env var para senha |
| Kubernetes | Kubernetes Secrets + volume/env ou External Secrets Operator | Idealmente integrado com Vault/Cloud Secret Manager |
O livro Advanced ASP.NET Core 8 Security coloca como melhor opção um armazenamento dedicado de chaves, como Azure Key Vault ou AWS Key Management Service; variáveis de ambiente são melhores que arquivos de configuração, mas ainda ficam no mesmo servidor.
Cuidados importantes
| Cuidado | Motivo |
|---|---|
| Não logar secrets | Logs vazam facilmente para Seq, Grafana, Sentry, CloudWatch etc. |
| Não retornar secrets em endpoint | Mesmo endpoint interno pode vazar |
Não commitar .env | .env costuma virar vazamento clássico |
| Não usar secret de produção em desenvolvimento | Reduz dano em máquina de dev comprometida |
| Validar config no startup | Falha rápido se falta secret |
| Separar permissões por ambiente | Dev não deveria acessar secret de produção |
| Rotacionar secrets | Reduz impacto de vazamento |
| Usar Managed Identity quando possível | Evita client secret para acessar outro secret |
| Cuidado com JWT | Payload de JWT é legível |
| Cuidado com command-line secrets | Pode aparecer em histórico ou inspeção de processo |
Exemplo completo de Program.cs
using System.ComponentModel.DataAnnotations;
using System.Text;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
var builder = WebApplication.CreateBuilder(args);
builder.Services
.AddOptions<JwtOptions>()
.Bind(builder.Configuration.GetSection(JwtOptions.SectionName))
.ValidateDataAnnotations()
.Validate(options => options.Secret.Length >= 32,
"Jwt:Secret deve ter pelo menos 32 caracteres.")
.ValidateOnStart();
builder.Services
.AddOptions<RabbitMqOptions>()
.Bind(builder.Configuration.GetSection(RabbitMqOptions.SectionName))
.ValidateDataAnnotations()
.ValidateOnStart();
JwtOptions jwtOptions = builder.Configuration
.GetSection(JwtOptions.SectionName)
.Get<JwtOptions>()
?? throw new InvalidOperationException("Jwt não configurado.");
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
byte[] key = Encoding.UTF8.GetBytes(jwtOptions.Secret);
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidIssuer = jwtOptions.Issuer,
ValidateAudience = true,
ValidAudience = jwtOptions.Audience,
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(key),
ValidateLifetime = true,
ClockSkew = TimeSpan.FromSeconds(30)
};
});
builder.Services.AddAuthorization();
var app = builder.Build();
app.UseAuthentication();
app.UseAuthorization();
app.MapGet("/health", () => Results.Ok("ok"));
app.Run();
public sealed class JwtOptions
{
public const string SectionName = "Jwt";
[Required]
public required string Issuer { get; init; }
[Required]
public required string Audience { get; init; }
[Required]
public required string Secret { get; init; }
[Range(1, 1440)]
public int ExpirationMinutes { get; init; } = 60;
}
public sealed class RabbitMqOptions
{
public const string SectionName = "RabbitMq";
[Required]
public required string Host { get; init; }
[Range(1, 65535)]
public int Port { get; init; } = 5672;
[Required]
public required string Username { get; init; }
[Required]
public required string Password { get; init; }
public string VirtualHost { get; init; } = "/";
}
Checklist prático
| Item | Recomendação |
|---|---|
appsettings.json | Pode ter estrutura, mas não secret real |
appsettings.Development.json | Pode ter valores locais não sensíveis |
| User Secrets | Use para desenvolvimento local |
| Variáveis de ambiente | Boa opção para containers e servidores |
| Docker Secrets | Melhor que env var em Swarm |
| Key Vault / Vault / Secret Manager | Melhor opção para produção robusta |
| Options Pattern | Use para tipagem forte e validação |
ValidateOnStart() | Use para falhar no startup se faltar secret |
| Logs | Nunca imprimir secrets |
| Git | .env, arquivos de secrets e dumps devem ficar no .gitignore |