¿Estás usando appsetings.json
o appsettings.development.json
para guardar información sensible? ¿Has “commiteado” alguna vez cadenas de conexión, secretos privados o llaves de aplicación por accidente?
Si la respuesta a las preguntas anteriores es afirmativa y nunca habías escuchado hablar de User Secrets
, entonces este artículo te puede interesar.
Contexto
En la mayoría de nuestras aplicaciones necesitamos interactuar con algún tipo de API, servicio o base de datos que requiere la utilización de credenciales ya sea en forma de cadena de conexión (connection string), llaves de API (API Keys) o secretos (secrets).
Cómo programadores, no nos corresponde ocuparnos de la gestión de esas credenciales en ambientes productivos, pero sí nos corresponde manejarlas y protegerlas correctamente en nuestros ambientes locales.
Si bien hay diferentes formas de manejar credenciales (variables de entorno para producción por ejemplo), es muy frecuente la utilización de archivos appsettings.json
y/o appsettings.development.json
.
Problema
Para ejecutar nuestra aplicación en forma local, solemos utilizar el archivo appsettings.development.json
para colocar aquellas configuraciones que dependen del ambiente, como por ejemplo las cadenas de conexión a base de datos. Por ejemplo:
{
"ConnectionStrings": {
"Database": "Server=dev.server;Database=Products;User Id=dev;Password=Passw0rd;"
},
"PricesEndpoint": "https://dev.server.com/api/prices?key=xkihf57845addg"
}
El problema con este método es que al momento de subir nuestros cambios al repositorio es muy fácil olvidarnos de este archivo e incluirlo como parte de la implementación.
.gitignore
como alternativa
Un posible solución al problema anterior es incluir el archivos appsettings.development.json
en el archivo .gitignore
de forma que git
ignore los cambios. En escenarios muy simples esto puede funcionar sin problemas.
Sin embargo, es común que haya ciertas configuraciones de facto que se van agregando o modificando con el tiempo, y que luego cada programador puede ir modificando en forma local según sea el caso. Por ejemplo:
{
// Default for development environment.
"UseCache": false
}
Esto puede resultar bastante tedioso y confuso de manejar si el archivo appsettings.development.json
se encuentra ignorado.
Solución: User Secrets
El Secret Manager es una funcionalidad desarrollada específicamente para manejar información sensible en ambientes locales de desarrollo de una forma más segura, y se ocupa de la gestión de los User Secrets.
Este guarda las piezas de información sensible dentro de un archivo que se encuentra en un directorio totalmente diferente al directorio del proyecto, de forma que nuestro sistema de control de cambios no lo vea como parte de las modificaciones.
¡Jamás volverás a “commitear” cadenas de conexión o secretos!
Activar User Secrets
Secret Manager funciona por proyecto, es decir, lo tenemos que activar en todos los proyectos donde lo necesitamos.
Podemos hacerlo mediante la línea de comandos utilizando .NET Cli
ejecutando el siguiente comando en la carpeta del proyecto:
dotnet user-secrets init
O también podemos hacerlos desde Visual Studio simplemente haciendo clic derecho sobre el proyecto y luego seleccionando la opción Administrar secretos de usuario:

Al activar los secretos de usuario, se genera una entrada en el archivo del proyecto (.csproj
) llamada UserSecretsId
con un guid
:
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<UserSecretsId>0dc8fc18-564f-4c6e-9602-65fae16ab231</UserSecretsId>
</PropertyGroup>
</Project>
Ese guid
corresponde al nombre de un directorio creado %APPDATA%\Microsoft\UserSecrets\
(Windows) o ~/.microsoft/usersecrets/
(Linux / MacOS), dentro del directorio encontraremos un archivo en formato JSON llamado secrets.json
con las configuraciones que hayamos agregado.

Agregar User Secrets
Podemos agregar secretos de usuario mediante el cliente .NET Cli
utilizando el siguiente comando:
dotnet user-secrets set "ConnectionStrings:Database" "Server=..."
dotnet user-secrets set "PricesEndpoint" "https://dev..."
En el primer comando los “:” (dos puntos) se utilizan para indicar los niveles de objetos de un JSON
También podemos hacerlo desde Visual Studio mediante la opción Administrar secretos de usuario como vimos anteriormente. La ventaja de esta alternativa es que Visual Studio nos permite manejar los secretos directamente como un archivo JSON
, exactamente igual al archivo appsettings.json
:
{
"ConnectionStrings": {
"Database": "Server=..."
},
"PricesEndpoint": "https://dev..."
}
Leer User Secrets
Los secretos de usuario se leen como cualquier otra configuración una vez que fueron cargados apropiadamente. Por ejemplo, podemos acceder a la configuración PricesEndpoint
mediante la interfaz IConfiguration
:
public class Startup
{
public Startup(IConfiguration configuration) =>
Configuration = configuration;
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
var pricesEndpoint = Configuration["PricesEndpoint"];
// O Configuration.GetConnectionString("Database")
var connectionString = Configuration["ConnectionStrings:Database"];
services.AddControllers();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
}
}
Para que esto funcione es necesario cargar primero los secretos de usuario. Si estamos utilizando Host.CreateDefaultBuilder(args)
en la clase Program.cs
, entonces no es necesario hacer nada mas ya que los secretos de usuario se cargar en forma automática.
public class Program
{
public static void Main(string[] args) =>
CreateHostBuilder(args).Build().Run();
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
En el caso de que no estemos utilizando Host.CreateDefaultBuilder(args)
o querramos cambiar el comportamiento, tenemos que utilizar el método de extensión AddUserSecrets()
.
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureAppConfiguration((hostContext, builder) =>
{
builder.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddJsonFile("appsettings.development.json", optional: true, reloadOnChange: true)
.AddUserSecrets<Program>(optional: true, reloadOnChange: true)
.AddEnvironmentVariables();
})
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
El orden en el que se incluyen las distintas fuentes de configuración es importante, ya que las ultimas sobrescriben a las primeras.
Debido a que los User Secrets sólo se usan en el ambiente local de cada desarrollador, lo recomendable es incluirlos sólo y sólo si el ambiente es Desarrollo:
Host.CreateDefaultBuilder(args)
.ConfigureAppConfiguration((hostContext, builder) =>
{
var environment = hostContext.HostingEnvironment;
// ...
if(environment.IsDevelopment())
{
builder.AddUserSecrets<Program>(optional: true, reloadOnChange: true);
}
// ...
})
// ...
Conclusión
En este artículo aprendimos que incluso en ambientes locales de desarrollo debemos manejar información sensible, como cadenas de conexión y secretos, con el mismo cuidado que lo hacemos en producción.
Además aprendimos cómo User Secrets puede ayudarnos a evitar dolores de cabeza en la gestión de información sensible en nuestros ambientes de desarrollo locales de una forma muy simple y práctica.