Prevent Secrets From Accidentally Being Committed to Source Control in ASP.NET Core Apps

One problem when dealing with developer “secrets” in development is accidentally checking them into source control. These secrets could be connection strings to dev resources, user IDs, product keys, etc.

To help prevent this from accidentally happening, the secrets can be stored outside of the project tree/source control repository. This means that when the code is checked in, there will be no secrets in the repository.

Each developer will have their secrets stored outside of the project code. When the app is run, these secrets can be retrieved at runtime from outside the project structure.

One way to accomplish this in ASP.NET Core  projects is to make use of the Microsoft.Extensions.SecretManager.Tools NuGet package to allow use of the command line tool. (also if you are targeting .NET Core 1.x , install the Microsoft.Extensions.Configuration.UserSecrets NuGet package).

Setting Up User Secrets

After creating a new ASP.NET Core project, add a tools reference to the NuGet package to the project, this will add the following item in the project file:

<DotNetCliToolReference Include="Microsoft.Extensions.SecretManager.Tools" Version="2.0.0" />

Build the project and then right click the project and you will see a new item called “Manage User Secrets” as the following screenshot shows:

Managing user secrets in Visual Studio

Clicking menu item will open a secrets.json file and also add an element named UserSecretsId to the project file. The content of this element is a GUID, the GUID is arbitrary but should be unique for each and every project.

<UserSecretsId>c83d8f04-8dba-4be4-8635-b5364f54e444</UserSecretsId>

User secrets will be stored in the secrets.json file which will be in %APPDATA%\Microsoft\UserSecrets\<user_secrets_id>\secrets.json on Windows or ~/.microsoft/usersecrets/<user_secrets_id>/secrets.json on Linux and macOS. Notice these paths contain the user_secrets_id that matches the GUID in the project file. In this way each project has a separate set of user secrets.

The secrets.json file contains key value pairs.

Managing User Secrets

User secrets can be added by editing the json file or by using the command line (from the project directory).

To list user secrets type: dotnet user-secrets list At the moment his will return ���No secrets configured for this application.”

To set (add) a secret: dotnet user-secrets set "Id" "42"

The secrets.json file now contains the following:

{
  "Id": "42"
}

Other dotnet user-secrets  commands include:

  • clear - Deletes all the application secrets
  • list - Lists all the application secrets
  • remove - Removes the specified user secret
  • set - Sets the user secret to the specified value

Accessing User Secrets in Code

To retrieve users secrets, in the startup class, access the item by key, for example:

public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc();

    var secretId = Configuration["Id"]; // returns 42
}

One thing to bear in mind is that secrets are not encrypted in the secrets.json file, as the documentation states: “The Secret Manager tool doesn't encrypt the stored secrets and shouldn't be treated as a trusted store. It's for development purposes only. The keys and values are stored in a JSON configuration file in the user profile directory.” & “You can store and protect Azure test and production secrets with the Azure Key Vault configuration provider.”

There’s a lot more information in the documentation and if you plan to use this tool you should read through it.

Testing ASP.NET Core Controllers in Isolation with Mock Objects and Moq

In previous posts we saw how to get started testing ASP.NET Core MVC controllers and also how to use the Moq mocking library in .NET Core tests.

If there is code in controllers that needs testing, but the controller has a dependency, for example passed into the constructor, it may not make sense to use the real version of the dependency. In these cases Moq can be used to create a mock version of the dependency and pass it to the controller that needs testing.

As an example suppose we have the following controller code:

public class HomeController : Controller
{
    private readonly ISmsGateway _smsGateway;

    public HomeController(ISmsGateway smsGateway)
    {
        _smsGateway = smsGateway;
    }

    [HttpPost]
    [ValidateAntiForgeryToken]
    public IActionResult Send(SendSmsRequest request)
    {
        if (ModelState.IsValid)
        {
            var sendReceipt = _smsGateway.Send(request.PhoneNumber, request.Message);

            return Ok(sendReceipt);
        }

        return BadRequest();
    }
}

In the preceding code, the controller takes an ISmsGateway dependency as a constructor parameter. This dependency is later used in the the Send() method.

After installing Moq a mock SMS gateway can be created. Once created, Moq’s Setup() method can be used to determine what happens when the controller calls the mocked Send() method as the following code demonstrates:

[Fact]
public void ShouldSendOk()
{
    SendSmsRequest sendSmsRequest = new SendSmsRequest
    {
        PhoneNumber = "42",
        Message = "Hello"
    };

    Guid expectedSendReceipt = Guid.NewGuid();

    var mockSmsGateway = new Mock<ISmsGateway>();
    
    mockSmsGateway.Setup(x => x.Send(sendSmsRequest.PhoneNumber, sendSmsRequest.Message))
                  .Returns(expectedSendReceipt);

    var sut = new HomeController(mockSmsGateway.Object);
    
    IActionResult result = sut.Send(sendSmsRequest);

    var okObjectResult = Assert.IsType<OkObjectResult>(result);

    Assert.Equal(expectedSendReceipt, okObjectResult.Value);
}

We may also want to test that if there is a model binding error, then  no message is sent via the SMS gateway. The follow test code shows the use of the AddModelError() method to simulate an error, and the use of Moq’s Verify() method to check that the gateway’s Send() method was never called:

[Fact]
public void ShouldNotSendWhenModelError()
{
    SendSmsRequest sendSmsRequest = new SendSmsRequest
    {
        PhoneNumber = "42",
        Message = "Hello"
    };

    var mockSmsGateway = new Mock<ISmsGateway>();

    var sut = new HomeController(mockSmsGateway.Object);
    sut.ModelState.AddModelError("Simulated", "Model error");

    sut.Send(sendSmsRequest);

    mockSmsGateway.Verify(x => x.Send(It.IsAny<string>(), It.IsAny<string>()), Times.Never);
}

To learn more about using Moq to create/configure/use mock objects check out my Mocking in .NET Core Unit Tests with Moq: Getting Started Pluralsight course, and to learn how to get started testing ASP.NET Core MVC applications check out my ASP.NET Core MVC Testing Fundamentals Pluralsight course.

Testing ASP.NET Core MVC Controllers: Getting Started

When writing ASP.NET Core MVC web applications, you may want to test that controller actions behave in the expected way, for example that the action returns the correct result type (e.g. a ViewResult) or that the action behaves as expected when the model state is invalid.

To get started writing controller tests, first add a new .NET Core xUnit test project to the solution. This will create the test project along with requried xUnit.net NuGet packages. It will also add a default test class "UnitTest1.cs":

using System;
using Xunit;

namespace WebApplication1.Tests
{
    public class UnitTest1
    {
        [Fact]
        public void Test1()
        {
        }
    }
}

In the preceding code, notice the xUnit.net [Fact] attribute that marks the Test1 method as a test that should be executed by the test runner. One way to run tests in Visual Studio is to use the built-in Test Explorer which can be accessed via the menus: Test->Windows->Test Explorer.

If you build the project you will see the default test shown in the Test Explorer window:

Visual Studio Test Explorer

Adding a Controller Test

First, to get access to the controllers in the ASP.NET Core MVC application, add a reference to the web project from the test project. An instance of a controller can now be created in the test method:

var sut = new WebApplication1.Controllers.HomeController();

We can now call methods (actions) on the controller and verify the results. As a simple example, we can check that the Index method result is a view:

[Fact]
public void Test1()
{
    HomeController sut = new WebApplication1.Controllers.HomeController();

    IActionResult result = sut.Index();

    Assert.IsType<ViewResult>(result);
}

There are many different ways to test the results of controllers, including the ability to simulate model errors or using Moq mock objects as controller constructor dependencies.

The following code shows an excerpt from a controller and a test that examines the view's model that was returned:

public class PersonViewModel
{
    public string Name { get; set; }
}

public IActionResult Person()
{
    PersonViewModel viewModel = new PersonViewModel
    {
        Name = "Amrit"
    };

    return View(viewModel);
}
[Fact]
public void Test2()
{
    HomeController sut = new WebApplication1.Controllers.HomeController();

    IActionResult result = sut.Person();

    ViewResult viewResult = Assert.IsType<ViewResult>(result);

    PersonViewModel model = Assert.IsType<PersonViewModel>(viewResult.Model);

    Assert.Equal("Amrit", model.Name);
}

To learn how to get started testing ASP.NET Core MVC applications check out my ASP.NET Core MVC Testing Fundamentals Pluralsight course.