Author : MD TAREQ HASSAN
App Secrets in aspnet core
- Caution:
- Passwords or other sensitive data should not be committed to source control
- Production secrets shouldn’t be used for development or test
- Secrets shouldn’t be deployed with the app
- Solution: Secret Manager
- For storing and retrieving sensitive data during development of an ASP.NET Core app on a development machine
- Never store passwords or other sensitive data in source code (hardcoded,
appsettings.json
)
- Development Time
- Secret Manager create
secrets.json
file that would not be committed into VCS - Configurations in
secrets.json
is available viaIConfiguration Configuration
(DI) Production: - Use Secrets should be made available in the production environment through a controlled means
- Use Environment variables
- Or use Azure Key Vault
- Secret Manager create
Social login providers assign Application Id and Application Secret tokens during the registration process.
The exact token names vary by provider. These tokens represent the credentials your app uses to access their API.
The tokens constitute the “secrets” that can be linked to your app configuration with the help of Secret Manager.
Secret Manager is a more secure alternative to storing the tokens in a configuration file, such as appsettings.json
.
User Secrets in Development
- Used for development only
- If managed identity is used, then all secrets can be fetched from KeyVault
- Use in case you need to override some settings/values from
appsettings.json
or KeyVault in your local debugging
- Right Click on the project > Manage User Secrets
- VS will create and open
secrets.json
- Right Click on the project > Edit Project Files >
UserSecretsId
section should be added
secrets.json
{
"Foo": {
"ConnectionString": "Server=(localdb)\\mssqllocaldb;Database=Movie-1;Trusted_Connection=True;MultipleActiveResultSets=true",
"ApiKey": "12345"
}
}
Accessing User Secrets
- User secrets configuration source is automatically added in development mode when the project calls
CreateDefaultBuilder
to initialize a new instance of the host with preconfigured defaults CreateDefaultBuilder
callsAddUserSecrets
when the EnvironmentName is Development- When
CreateDefaultBuilder
isn’t called, add the user secrets configuration source explicitly by callingAddUserSecrets
User secrets can be retrieved via the Configuration API:
public class Startup
{
public IConfiguration Configuration { get; }
private string _apiKey = null;
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public void ConfigureServices(IServiceCollection services)
{
_apiKey = Configuration["Foo:ApiKey"];
}
public void Configure(IApplicationBuilder app)
{
// use _apiKey here if needed
}
}
See
App Services Managed Identity
- Managed Identity will be usued to access KeyVault
- What will happen:
- When Managed Identity is activated, Azure App Services is registered in Azure AD
- When deployed app (deployed to Azure App Services) tries to access Azure KeyVault, KeyVault will ask for authentication
- Then, Azure App Services will use Azure AD credential and therefore deployed app will be able to get secrets from Azure KeyVault
Following setting is for activating Managed Identity in Azure App Services
AKS Pod Identity
See: Azure Kubernetes Service - managed-pod identity
Accessing Azure KeyVault
- When managed identity is configured (App Service Managed Identity or AKS Pod Identity), Getting security token from Azure AD and accessing KeyVault will happen automatically
- Managed identity (user managed identity or system managed identity) must be assigned appropriate access permission to Azure KeyVault
- Visual Studio debugging (Development)
- Add your Azure account to Visual Studio
- Visual Studio manage access to KeyVault (using your Azure account)
- Production (AKS or Azure App Service)
- AKS Pod managed identity will be used to access to KeyVault
- App Service Managed identity will be used to access to KeyVault
- Nuget Packages
- New packages
Azure.Extensions.AspNetCore.Configuration.Secrets
Azure.Identity
- Deprecated packages (don’t use)
Microsoft.Azure.Services.AppAuthentication
: https://www.nuget.org/packages/Microsoft.Azure.Services.AppAuthenticationMicrosoft.Azure.KeyVault
: https://www.nuget.org/packages/Microsoft.Azure.KeyVault
- New packages
Install nuget packages
Install-Package Azure.Identity
Install-Package Azure.Extensions.AspNetCore.Configuration.Secrets
Implementation Code
KeyVault.cs
public class KeyVault
{
private KeyVault(){}
public const string KeyVaultNameIdentifierKey = "KeyVaultNameIdentifierKey";
public const string DBConnectionStringSecretIdentifierKey = "DBConnectionStringSecretIdentifierKey";
public const string KeyVaultAccessErrorMessage = "KeyVaultAccessErrorMessage";
public const string TestSecretName = "test-secret";
}
appsettings.json
{
"KeyVaultNameIdentifierKey": "KeyVaultName",
"KeyVaultName": "my-keyvault",
"DBConnectionStringSecretIdentifierKey": "AzureSQLDBConnectionString"
}
Program.cs
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using Azure.Security.KeyVault.Secrets;
using Azure.Identity;
using Azure.Extensions.AspNetCore.Configuration.Secrets;
using System;
using static MyApp.Constants.KeyVault;
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args).ConfigureAppConfiguration((context, config) =>
{
var builtConfig = config.Build();
try
{
var keyVaultName = builtConfig[builtConfig[KeyVaultNameIdentifierKey]];
var secretClient = new SecretClient(new Uri($"https://{keyVaultName}.vault.azure.net/"), new DefaultAzureCredential());
config.AddAzureKeyVault(secretClient, new KeyVaultSecretManager());
#region Debugging KeyVault Secret
//
// Following code is for debugging purpose only
// Do not uncomment
//
//builtConfig = config.Build();
//var secrectName = "test-secret";
//var testSecret = builtConfig[secrectName];
//throw new System.ApplicationException(message: "Intentional exception");
#endregion
}
catch (System.Exception ex)
{
builtConfig[KeyVaultAccessErrorMessage] = ex.Message;
}
})
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
Startup.cs
using System;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using static MyApp.Constants.KeyVault;
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
#region AddDbContext
var dbConnectionString = Configuration[Configuration[DBConnectionStringSecretIdentifierKey]];
if (string.IsNullOrEmpty(dbConnectionString))
{
throw new ApplicationException(message: "Failed to get database connection string");
}
services.AddDbContext<PrescriptionContext>(options => options.UseSqlServer(dbConnectionString));
#endregion
services.AddControllers();
services.AddCors(options =>
{
options.AddPolicy("CorsPolicy",
builder => builder.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader()
);
});
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseCors("CorsPolicy");
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
TestController.cs
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using System;
using static MyApp.Constants.KeyVault;
public class Test
{
public string Message { get; set; }
public string KeyVaultTestSecret { get; set; }
}
[ApiController]
[Route("/")]
public class TestController : ControllerBase
{
[HttpGet]
public Test Get()
{
var message = Configuration[KeyVaultAccessErrorMessage];
if (string.IsNullOrEmpty(message))
{
message = $"It's working. Execution time: {DateTime.Now.ToString("HH:mm")}";
}
return new Test { Message = message, KeyVaultTestSecret = Configuration [TestSecretName] };
}
#region Ctor and Props
private readonly ILogger<WeatherForecastController> _logger;
public IConfiguration Configuration { get; }
public TestController(ILogger<WeatherForecastController> logger, IConfiguration configuration)
{
_logger = logger;
Configuration = configuration;
}
#endregion
}
Links
- https://docs.microsoft.com/en-us/aspnet/core/security/key-vault-configuration?view=aspnetcore-5.0#use-managed-identities-for-azure-resources
- https://github.com/dotnet/AspNetCore.Docs/blob/main/aspnetcore/security/key-vault-configuration/samples/3.x/SampleApp/Program.cs
- https://github.com/Azure/azure-sdk-for-net/tree/master/sdk/identity/Azure.Identity#authenticating-with-the-defaultazurecredential
- https://github.com/Azure/azure-sdk-for-net/tree/master/sdk/keyvault/Azure.Security.KeyVault.Secrets#retrieve-a-secret