Guía completa para externalizar la configuración y los secretos de tu backend .NET usando la arquitectura de referencia de Microsoft: App Configuration como almacén central, Key Vault para los secretos y Managed Identities para autenticarte sin contraseñas. Al final, tu
appsettings.jsonquedará prácticamente vacío.
1. El problema: tu appsettings.json no es un almacén de secretos
El escenario clásico de todo backend .NET:
{
"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"TenantId": "TU_TENANT_ID",
"ClientId": "TU_CLIENT_ID",
"Audience": "api://TU_CLIENT_ID"
},
"ConnectionStrings": {
"CosmosDb": "AccountEndpoint=https://....documents.azure.com:443/;AccountKey=..."
}
}
Ese archivo termina en el repositorio, en el historial de git y en la máquina de cada desarrollador. Las alternativas intermedias tampoco resuelven el problema de raíz: los user secrets o las variables de entorno siguen almacenándose localmente y sin cifrar en la máquina. Son útiles en desarrollo, pero no son una solución de producción.
La arquitectura de referencia recomendada por Microsoft combina tres servicios:
| Servicio | Qué aporta |
|---|---|
| Azure App Configuration | Almacén central de configuración (pares clave-valor), feature flags y soporte de labels por entorno |
| Azure Key Vault | Almacén de secretos con cifrado a nivel de hardware, renovación automática de certificados y control de acceso granular |
| Managed Identity | Identidad administrada por Entra ID que autentica tus servicios sin gestionar ninguna credencial |
El objetivo final: ninguna credencial ni configuración sensible vive en tu código, y el despliegue no requiere pasar secretos por pipelines.
2. Cómo funciona el flujo de arranque
Al arrancar, ocurre esto:
- Tu backend usa su Managed Identity (la identidad de la máquina/servicio donde corre) para autenticarse contra App Configuration sin usuario ni contraseña.
- Descarga la configuración (por ejemplo, la sección
AzureAd) en memoria. - Cuando una clave es una Key Vault Reference, App Configuration la resuelve automáticamente contra Key Vault, también autenticado con la misma identidad.
- Todo queda disponible en
IConfigurationcomo si fuera unappsettings.jsoncualquiera: el resto del código no cambia.
La gran ventaja de poner App Configuration delante de Key Vault es esa transparencia: lees valores normales y secretos exactamente igual, aunque por detrás se usen mecanismos de acceso distintos.
3. Paso 1 — Crear los recursos en Azure
3.1 App Configuration
Desde el portal: Create a resource → Azure App Configuration. Elige SKU:
- Free: sin SLA, limitada a 1.000 peticiones/día — suficiente para desarrollo.
- Standard: para producción, con SLA.
Si usas .NET Aspire, el recurso se declara en el AppHost con
builder.AddAzureAppConfiguration("config").WithParameter("sku", "Standard")y se aprovisiona solo al arrancar.
3.2 Key Vault y su modelo de permisos
Crea un Key Vault (Create a resource → Key Vault). Un detalle importante durante la creación: Key Vault soporta dos modelos de permisos:
- Vault access policy: el modelo antiguo (legacy).
- Azure RBAC: el modelo recomendado, basado en roles de Entra ID.
Usa siempre Azure role-based access control. Además, marca la casilla de acceso para plantillas ARM/Bicep si vas a desplegar con infraestructura como código.
Después crea tus secretos en Objects → Secrets. Además del valor, puedes definir fechas de activación y expiración — útil para rotaciones planificadas.
4. Paso 2 — Managed Identity y permisos RBAC
4.1 Tipos de identidad administrada
Las managed identities (formalmente, Microsoft Entra managed identities for Azure resources) abstraen los service principals: se crean y eliminan solas. Hay dos tipos:
System-assigned (asignada al sistema):
- Ciclo de vida ligado al recurso: si borras el App Service / Container App, la identidad y sus roles desaparecen con él.
- Ideal cuando cada servicio tiene permisos distintos.
User-assigned (asignada por el usuario):
- Es un recurso independiente; puede compartirse entre varios servicios.
- Si borras un servicio, la identidad sobrevive.
- Contras: hay que indicar explícitamente cuál usar (su
ClientId) porque un servicio puede tener varias.
Para un backend único en App Service o Container Apps, lo más simple es system-assigned. Para conjuntos de microservicios que comparten permisos, una user-assigned compartida evita repetir asignaciones.
4.2 Asignar los roles
En el portal, ve a tu backend (App Service, Container Apps...) → Identity → activa System assigned. Luego, en cada recurso de destino, asigna roles:
En App Configuration → Access control (IAM) → Add role assignment:
| Rol | Cuándo usarlo |
|---|---|
| App Configuration Data Reader | El caso habitual: la app solo lee configuración (mínimo privilegio) |
| App Configuration Data Owner | Solo si la propia app escribe configuración en tiempo de ejecución |
En Key Vault → Access control (IAM):
| Rol | Cuándo usarlo |
|---|---|
| Key Vault Secrets User | La app: leer secretos (no puede crearlos ni modificarlos) |
| Key Vault Secrets Officer | Operadores: gestionan secretos sin poder leerlos |
| Key Vault Contributor | Pipelines de despliegue: crean secretos sin poder leerlos |
Este reparto refleja el principio de menor privilegio (PoLP): cada identidad tiene exactamente los permisos que necesita, ni uno más. Si además usas la misma identidad para desplegar (escribir secretos/config), valora identidades separadas para despliegue y ejecución.
Importante: asigna el rol App Configuration Data Reader también a tu propio usuario para poder desarrollar y depurar en local. Los cambios de roles pueden tardar unos 15 minutos en propagarse.
5. Paso 3 — Guardar la configuración en App Configuration
5.1 Claves jerárquicas emulando el JSON
En Configuration explorer, agrega claves usando : como separador, igual que la estructura del JSON:
| Clave (Key) | Valor (Value) |
|---|---|
AzureAd:Instance |
https://login.microsoftonline.com/ |
AzureAd:Domain |
tuempresa.onmicrosoft.com |
AzureAd:TenantId |
TU_TENANT_ID |
AzureAd:ClientId |
TU_CLIENT_ID_DEL_BACKEND |
AzureAd:Audience |
api://TU_CLIENT_ID_DEL_BACKEND |
Dos buenas prácticas:
- Prefijo por servicio: si varios servicios comparten el almacén, usa la primera parte de la clave como nombre del servicio (
MiBackend:AzureAd:...). Así sabes a qué servicio pertenece cada valor y puedes filtrar al descargar (ver paso 4). - Valores JSON agrupados: un valor puede ser JSON completo, lo que reduce el número de claves y peticiones.
5.2 Secretos como Key Vault References
Los datos sensibles (cadenas de conexión, API keys) no van como valor plano. Se guardan como secretos en Key Vault y en App Configuration se crea una Key Vault Reference:
- Copia el URI del secreto desde Key Vault (ej.
https://mi-vault.vault.azure.net/secrets/cosmos-connection). - En App Configuration → Configuration explorer → Create → Key vault reference, usa la misma clave jerárquica (
ConnectionStrings:CosmosDb) y pega el URI como valor.
Resultado: la clave existe en App Configuration, pero el valor real vive cifrado en Key Vault y se resuelve en tiempo de lectura.
6. Paso 4 — Implementación en .NET
6.1 Paquetes NuGet
dotnet add package Microsoft.Azure.AppConfiguration.AspNetCore
dotnet add package Azure.Identity
6.2 El appsettings.json mínimo
Solo necesita saber dónde está App Configuration:
{
"ConnectionStrings": {
"AppConfig": "https://tu-appconfig.azconfig.io"
},
"AllowedHosts": "*"
}
Si despliegas en App Service o Container Apps, incluso esta URL puedes ponerla en las variables de entorno/secrets del portal, dejando el archivo completamente vacío.
6.3 Program.cs completo
using Azure.Identity;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.Identity.Web;
var builder = WebApplication.CreateBuilder(args);
// 1. Credenciales: en producción resuelve la Managed Identity,
// en local prueba Visual Studio / Azure CLI, etc.
var credential = new DefaultAzureCredential();
// 2. Endpoint de App Configuration (del appsettings mínimo o de env vars)
string endpoint = builder.Configuration.GetConnectionString("AppConfig")
?? throw new InvalidOperationException("Falta la URL de App Configuration");
// 3. Conectar App Configuration + Key Vault
builder.Configuration.AddAzureAppConfiguration(options =>
{
options.Connect(new Uri(endpoint), credential)
// Filtra solo las claves de este servicio (buena práctica)
.Select("MiBackend*", LabelFilter.Null)
.Select("MiBackend*", builder.Environment.EnvironmentName)
// CRÍTICO para resolver Key Vault References:
.ConfigureKeyVault(kv =>
{
kv.SetCredential(credential);
});
});
// 4. Autenticación Entra ID: lee "AzureAd" desde App Configuration de forma transparente
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApi(builder.Configuration.GetSection("MiBackend:AzureAd"));
builder.Services.AddAuthorization();
builder.Services.AddControllers();
var app = builder.Build();
app.UseHttpsRedirection();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.Run();
Detalles que marcan la diferencia:
.ConfigureKeyVault(kv => kv.SetCredential(credential)): sin esta línea, las Key Vault References fallan en runtime. Define qué credenciales se usan para acceder a Key Vault — normalmente las mismas que para App Configuration, aunque pueden ser otras..Select("MiBackend*"): filtra por prefijo. Descargar todas las claves de todos los servicios desperdicia peticiones (y en SKU Free, cuota).- Doble
.Selectcon label: carga primero las claves sin etiqueta y luego las del entorno actual. Si un valor está duplicado, gana el último — así una clave con labelDevelopmentsobrescribe la genérica solo en ese entorno.
6.4 Leer valores: normal y secreto, idéntico
app.MapGet("/debug/config", (IConfiguration config) =>
{
var audience = config["MiBackend:AzureAd:Audience"];
var cosmos = config.GetConnectionString("CosmosDb"); // viene de Key Vault
return Results.Ok(new { audience, cosmos });
});
No hay diferencia entre leer un valor normal y un secreto: ambos llegan por IConfiguration, aunque por detrás se usen mecanismos de acceso completamente distintos.
Y aquí está la elegancia del diseño: AddMicrosoftIdentityWebApi(builder.Configuration.GetSection("...AzureAd")) no sabe ni le importa de dónde vienen los valores. Como App Configuration ya los cargó en la cadena de proveedores de configuración de .NET, la validación de tokens JWT funciona sin cambiar una sola línea del código de autenticación.
7. Desarrollo local: cómo piensa DefaultAzureCredential
DefaultAzureCredential no encuentra una Managed Identity en tu portátil, así que prueba fuentes de credenciales en orden hasta que una funcione:
| # | Credencial | Cuándo aplica |
|---|---|---|
| 1 | EnvironmentCredential | Variables de entorno con client/tenant ID (service principals) |
| 2 | WorkloadIdentityCredential | Azure Kubernetes Service con identidades de workload |
| 3 | ManagedIdentityCredential | Producción en Azure: la identidad de la máquina |
| 4 | SharedTokenCacheCredential | Legacy, sustituida por la de Visual Studio |
| 5 | VisualStudioCredential | Sesión iniciada en Visual Studio (Herramientas → Opciones → Azure) |
| 6 | VisualStudioCodeCredential | Extensión de Azure Account de VS Code |
| 7 | AzureCliCredential | Sesión de az login en tu terminal |
Traducción práctica para desarrollo local: haz login una vez con az login (o inicia sesión en Visual Studio), asegúrate de que tu usuario tiene el rol App Configuration Data Reader y Key Vault Secrets User, y listo — el mismo código funciona en local y en producción sin cambios.
Si algo falla, DefaultAzureCredential produce errores confusos ("DefaultAzureCredential failed to retrieve a token..."). La guía oficial de troubleshooting detalla cómo activar diagnóstico y habilitar/deshabilitar credenciales concretas para aislar el problema:
var credential = new DefaultAzureCredential(new DefaultAzureCredentialOptions
{
ExcludeEnvironmentCredential = true,
ExcludeWorkloadIdentityCredential = true,
// Excluye fuentes hasta dar con la que falla
});
8. Bonus I: entornos con labels
.NET distingue entornos cargando appsettings.Development.json o appsettings.Production.json. En App Configuration el equivalente son los labels: crea la misma clave con distintas etiquetas (Development, Production) y cárgalas según el entorno:
options.Connect(new Uri(endpoint), credential)
.Select("MiBackend*", LabelFilter.Null) // base común
.Select("MiBackend*", builder.Environment.EnvironmentName); // override por entorno
Buena práctica: separa producción y desarrollo en suscripciones distintas (idealmente con tenants de Entra ID distintos) y por tanto en recursos de App Configuration diferentes. Los labels son para entornos que comparten almacén (p. ej., staging y production en la misma suscripción).
9. Bonus II: Feature Flags en vivo
Ya que tienes App Configuration conectada, los feature flags salen gratis: activar/desactivar funcionalidades en runtime sin redeploy.
9.1 Configuración en Azure
En App Configuration → Feature manager → crea un flag (ej. FeatureExportacionPDF). Los dos filtros integrados más útiles:
- Targeting filter: abre la funcionalidad a un grupo concreto de usuarios (early adopters) o a un porcentaje aleatorio (despliegue canary).
- Time window filter: activa la funcionalidad entre fechas de inicio y fin (lanzamientos programados).
También puedes implementar filtros propios.
9.2 Cablearlo en .NET
dotnet add package Microsoft.FeatureManagement.AspNetCore
using Microsoft.FeatureManagement;
// DI: registrar Feature Management y los filtros que uses
builder.Services.AddFeatureManagement()
.AddFeatureFilter<TargetingFilter>()
.AddFeatureFilter<TimeWindowFilter>();
// En el AddAzureAppConfiguration de antes, añade:
options.Connect(new Uri(endpoint), credential)
.Select("MiBackend*")
.ConfigureKeyVault(kv => kv.SetCredential(credential))
.UseFeatureFlags(); // <- carga también los feature flags
// Middleware (necesario para evaluación dinámica de flags):
app.UseAzureAppConfiguration();
9.3 Consumirlos
group.MapPost("/export", async (
ExportRequest request,
IFeatureManager featureManager,
CancellationToken ct) =>
{
if (!await featureManager.IsEnabledAsync("FeatureExportacionPDF"))
{
return Results.BadRequest("La exportación PDF no está disponible actualmente.");
}
// ... lógica de exportación
});
IFeatureManager.IsEnabledAsync evalúa el flag aplicando sus filtros (targeting, time window...). Combinado con UseAzureAppConfiguration(), apagar una funcionalidad problemática en producción es un click en el portal — sin despliegues, sin reinicios.
10. Checklist final
- Recurso de App Configuration creado (SKU según entorno)
- Key Vault creado con modelo de permisos Azure RBAC
- Secretos creados en Key Vault y referenciados desde App Configuration (Key Vault References)
- Claves jerárquicas con prefijo por servicio (
MiServicio:Seccion:Clave) - Managed Identity habilitada en el hosting (system o user-assigned)
- Rol App Configuration Data Reader asignado a la identidad del backend y a tu usuario
- Rol Key Vault Secrets User asignado a la identidad del backend
- Paquetes
Microsoft.Azure.AppConfiguration.AspNetCoreyAzure.Identityinstalados -
.ConfigureKeyVault(kv => kv.SetCredential(credential))presente en elProgram.cs -
.Select()filtra por prefijo de servicio - Login local funcionando (
az logino sesión de Visual Studio) - (Opcional)
UseFeatureFlags()+UseAzureAppConfiguration()+AddFeatureManagement()
11. Conclusión
Esta arquitectura convierte la configuración en un recurso operado, no en archivos: rotas secretos sin tocar código, cambias comportamiento por entorno sin builds distintos, apagas features sin desplegar y —lo más importante— tu repositorio deja de ser superficie de ataque. La inversión inicial (media hora de portal + 20 líneas en Program.cs) se paga la primera vez que alguien pega una cadena de conexión en un commit.
El mismo código funciona en desarrollo y producción gracias a DefaultAzureCredential: en tu máquina usa tus credenciales de CLI/IDE; en Azure, la identidad de la plataforma. Ninguna contraseña, en ningún sitio, nunca.
Fuentes
Basado en el contenido de la knowledge base personal:
- Pragmatic Microservices with C# and Azure (Christian Nagel) — caps. 7–9: Flexible Configurations, CI/CD y feature flags, Authentication con Microsoft Identity
- .NET Microservices Architecture for Containerized .NET Applications (Microsoft) — protección de secretos en producción
- Web API Development with ASP.NET Core 8 — proveedores de configuración (Key Vault, App Configuration)
- ASP.NET Core 9 Essentials — configuración dinámica y feature toggles
- Building Modern SaaS Applications with C# and .NET — mejores prácticas de autenticación y secretos
- Systems Programming with C# and .NET — uso de Azure Key Vault