Author : MD TAREQ HASSAN

App Secrets in aspnet core

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

secrets.json

{
  "Foo": {
    "ConnectionString": "Server=(localdb)\\mssqllocaldb;Database=Movie-1;Trusted_Connection=True;MultipleActiveResultSets=true",
    "ApiKey": "12345"
  }
}

Accessing User Secrets

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

Following setting is for activating Managed Identity in Azure App Services

Azure App Services Configuration for Azure KeyVault with Managed Identity Step 1

Azure App Services Configuration for Azure KeyVault with Managed Identity Step 2

Azure App Services Configuration for Azure KeyVault with Managed Identity Step 3

AKS Pod Identity

See: Azure Kubernetes Service - managed-pod identity

Accessing Azure KeyVault

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
}