Skip to main content

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.json
  • appsettings.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 aproximadaFonteUso comum
1appsettings.jsonConfiguração base
2appsettings.{Environment}.jsonConfiguração por ambiente
3User SecretsSegredos locais em desenvolvimento
4Variáveis de ambienteConfiguração em servidor/container
5Command-line argumentsOverride 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:

RecursoMelhor uso
Azure Key VaultSecrets
Azure App ConfigurationConfigurações não sensíveis, feature flags, parâmetros dinâmicos
App Configuration + Key VaultConfig 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

TipoQuando usarObservação
IOptions<T>Configuração estávelSingleton, lê uma vez
IOptionsSnapshot<T>Configuração por requestScoped, útil em ASP.NET Core
IOptionsMonitor<T>Configuração que pode mudar em runtimeSingleton, 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

AmbienteForma recomendadaObservação
Desenvolvimento localUser SecretsSimples e integrado ao .NET SDK
Testes locais com Docker.env fora do Git ou Docker secretsEvite commitar .env
HomologaçãoVariáveis de ambiente, Vault ou Key VaultDepende da infra
Produção simplesVariáveis de ambiente protegidasAceitável, mas exige controle de acesso
Produção mais robustaKey Vault, HashiCorp Vault, AWS Secrets Manager, GCP Secret ManagerMelhor para auditoria e rotação
Docker SwarmDocker secrets + KeyPerFileMelhor que env var para senha
KubernetesKubernetes Secrets + volume/env ou External Secrets OperatorIdealmente 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

CuidadoMotivo
Não logar secretsLogs vazam facilmente para Seq, Grafana, Sentry, CloudWatch etc.
Não retornar secrets em endpointMesmo endpoint interno pode vazar
Não commitar .env.env costuma virar vazamento clássico
Não usar secret de produção em desenvolvimentoReduz dano em máquina de dev comprometida
Validar config no startupFalha rápido se falta secret
Separar permissões por ambienteDev não deveria acessar secret de produção
Rotacionar secretsReduz impacto de vazamento
Usar Managed Identity quando possívelEvita client secret para acessar outro secret
Cuidado com JWTPayload de JWT é legível
Cuidado com command-line secretsPode 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

ItemRecomendação
appsettings.jsonPode ter estrutura, mas não secret real
appsettings.Development.jsonPode ter valores locais não sensíveis
User SecretsUse para desenvolvimento local
Variáveis de ambienteBoa opção para containers e servidores
Docker SecretsMelhor que env var em Swarm
Key Vault / Vault / Secret ManagerMelhor opção para produção robusta
Options PatternUse para tipagem forte e validação
ValidateOnStart()Use para falhar no startup se faltar secret
LogsNunca imprimir secrets
Git.env, arquivos de secrets e dumps devem ficar no .gitignore